diff --git a/Prism/src/main/java/network/darkhelmet/prism/Prism.java b/Prism/src/main/java/network/darkhelmet/prism/Prism.java index 6134bc1c7..71d372074 100644 --- a/Prism/src/main/java/network/darkhelmet/prism/Prism.java +++ b/Prism/src/main/java/network/darkhelmet/prism/Prism.java @@ -24,6 +24,7 @@ import network.darkhelmet.prism.listeners.PrismBlockEvents; import network.darkhelmet.prism.listeners.PrismCustomEvents; import network.darkhelmet.prism.listeners.PrismEntityEvents; +import network.darkhelmet.prism.listeners.PrismExplodeEvents; import network.darkhelmet.prism.listeners.PrismInventoryEvents; import network.darkhelmet.prism.listeners.PrismInventoryMoveItemEvent; import network.darkhelmet.prism.listeners.PrismPlayerEvents; @@ -527,6 +528,7 @@ private void enabled() { // Assign event listeners getServer().getPluginManager().registerEvents(new PrismBlockEvents(this), this); + getServer().getPluginManager().registerEvents(new PrismExplodeEvents(this), this); getServer().getPluginManager().registerEvents(new PrismEntityEvents(this), this); getServer().getPluginManager().registerEvents(new PrismWorldEvents(), this); getServer().getPluginManager().registerEvents(new PrismPlayerEvents(this), this); diff --git a/Prism/src/main/java/network/darkhelmet/prism/PrismConfig.java b/Prism/src/main/java/network/darkhelmet/prism/PrismConfig.java index 55ee74532..c47c94426 100644 --- a/Prism/src/main/java/network/darkhelmet/prism/PrismConfig.java +++ b/Prism/src/main/java/network/darkhelmet/prism/PrismConfig.java @@ -190,6 +190,7 @@ public FileConfiguration getConfig() { config.addDefault("prism.tracking.tnt-explode", true); config.addDefault("prism.tracking.bed-explode", true); config.addDefault("prism.tracking.respawnanchor-explode", true); + config.addDefault("prism.tracking.block-explode", true); config.addDefault("prism.tracking.tnt-prime", true); config.addDefault("prism.tracking.tree-grow", true); config.addDefault("prism.tracking.vehicle-break", true); diff --git a/Prism/src/main/java/network/darkhelmet/prism/actionlibs/ActionRegistry.java b/Prism/src/main/java/network/darkhelmet/prism/actionlibs/ActionRegistry.java index 4691d0073..07fcb32a7 100644 --- a/Prism/src/main/java/network/darkhelmet/prism/actionlibs/ActionRegistry.java +++ b/Prism/src/main/java/network/darkhelmet/prism/actionlibs/ActionRegistry.java @@ -273,6 +273,8 @@ private void registerPrismDefaultActions() { BlockAction.class, Il8nHelper.getRawMessage("blew-up"))); registerAction(new ActionTypeImpl("respawnanchor-explode", false, true, true, BlockAction.class, Il8nHelper.getRawMessage("blew-up"))); + registerAction(new ActionTypeImpl("block-explode", false, true, true, + BlockAction.class, Il8nHelper.getRawMessage("blew-up"))); registerAction(new ActionTypeImpl("tnt-prime", false, false, false, UseAction.class, Il8nHelper.getRawMessage("primed"))); registerAction(new ActionTypeImpl("tree-grow", true, true, true, diff --git a/Prism/src/main/java/network/darkhelmet/prism/listeners/BaseListener.java b/Prism/src/main/java/network/darkhelmet/prism/listeners/BaseListener.java deleted file mode 100644 index fcffcde36..000000000 --- a/Prism/src/main/java/network/darkhelmet/prism/listeners/BaseListener.java +++ /dev/null @@ -1,54 +0,0 @@ -package network.darkhelmet.prism.listeners; - -import network.darkhelmet.prism.Prism; -import network.darkhelmet.prism.actionlibs.ActionFactory; -import network.darkhelmet.prism.actionlibs.RecordingQueue; -import network.darkhelmet.prism.utils.MaterialTag; -import network.darkhelmet.prism.utils.block.Utilities; -import org.bukkit.Material; -import org.bukkit.block.Block; -import org.bukkit.block.data.Bisected; -import org.bukkit.block.data.type.Door; -import org.bukkit.event.Listener; - -import java.util.List; - -public abstract class BaseListener implements Listener { - - protected final Prism plugin; - - protected BaseListener(Prism plugin) { - this.plugin = plugin; - } - - protected void contructBlockEvent(final String parentAction, final String cause, final List blockList) { - final PrismBlockEvents be = new PrismBlockEvents(plugin); //todo is this necessary? - for (Block block : blockList) { - // don't bother record upper doors. - if (MaterialTag.DOORS.isTagged(block.getType()) - && ((Door) block.getState().getBlockData()).getHalf() == Bisected.Half.TOP) { - continue; - } - - // Change handling a bit if it's a long block - final Block sibling = Utilities.getSiblingForDoubleLengthBlock(block); - if (sibling != null && !block.getType().equals(Material.CHEST) - && !block.getType().equals(Material.TRAPPED_CHEST)) { - block = sibling; - } - - // log items removed from container - // note: done before the container so a "rewind" for rollback will - // work properly - final Block b2 = block; - be.forEachItem(block, (i, s) -> RecordingQueue.addToQueue(ActionFactory.createItemStack("item-remove", - i, i.getAmount(), 0, null, b2.getLocation(), cause))); - // be.logItemRemoveFromDestroyedContainer( name, block ); - RecordingQueue.addToQueue(ActionFactory.createBlock(parentAction, block, cause)); - // look for relationships - be.logBlockRelationshipsForBlock(cause, block); - - } - } - -} diff --git a/Prism/src/main/java/network/darkhelmet/prism/listeners/PrismBlockEvents.java b/Prism/src/main/java/network/darkhelmet/prism/listeners/PrismBlockEvents.java index 0c3bf8d04..46d336fb9 100644 --- a/Prism/src/main/java/network/darkhelmet/prism/listeners/PrismBlockEvents.java +++ b/Prism/src/main/java/network/darkhelmet/prism/listeners/PrismBlockEvents.java @@ -1,7 +1,5 @@ package network.darkhelmet.prism.listeners; -import com.google.common.cache.Cache; -import com.google.common.cache.CacheBuilder; import network.darkhelmet.prism.Prism; import network.darkhelmet.prism.actionlibs.ActionFactory; import network.darkhelmet.prism.actionlibs.RecordingQueue; @@ -16,7 +14,6 @@ import org.bukkit.block.DoubleChest; import org.bukkit.block.Jukebox; import org.bukkit.block.Sign; -import org.bukkit.block.data.type.Bed; import org.bukkit.block.data.type.Chest; import org.bukkit.block.data.type.Chest.Type; import org.bukkit.block.sign.Side; @@ -25,10 +22,10 @@ import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; import org.bukkit.event.block.BlockBreakEvent; import org.bukkit.event.block.BlockBurnEvent; import org.bukkit.event.block.BlockDispenseEvent; -import org.bukkit.event.block.BlockExplodeEvent; import org.bukkit.event.block.BlockFadeEvent; import org.bukkit.event.block.BlockFormEvent; import org.bukkit.event.block.BlockFromToEvent; @@ -41,7 +38,6 @@ import org.bukkit.event.block.SignChangeEvent; import org.bukkit.event.entity.EntityChangeBlockEvent; import org.bukkit.event.player.PlayerBedEnterEvent; -import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.inventory.InventoryHolder; import org.bukkit.inventory.ItemStack; @@ -49,28 +45,19 @@ import java.util.List; import java.util.Locale; import java.util.WeakHashMap; -import java.util.concurrent.TimeUnit; import java.util.function.BiConsumer; import java.util.function.Consumer; -public class PrismBlockEvents extends BaseListener { +public class PrismBlockEvents implements Listener { - private final Cache bedWeakCache = CacheBuilder - .newBuilder() - .expireAfterWrite(30, TimeUnit.SECONDS) - .build(); - private final Cache anchorWeakCache = CacheBuilder - .newBuilder() - .expireAfterWrite(30, TimeUnit.SECONDS) - .build(); + private final Prism plugin; /** * Constructor. - * - * @param plugin Prism. + * @param plugin Prism */ public PrismBlockEvents(Prism plugin) { - super(plugin); + this.plugin = plugin; if (Prism.getInstance().getServerMajorVersion() >= 20) { try { @@ -365,63 +352,11 @@ public void onLeavesDecay(final LeavesDecayEvent event) { } /** - * Primarily for tracking respawn anchor explosions in the world and end, - * and bed explosions in the nether and end. - * @param event BlockExplodeEvent - */ - @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - public void onBlockExplode(BlockExplodeEvent event) { - if (event.getBlock().getBlockData() instanceof Bed) { - if (!Prism.getIgnore().event("bed-explode", event.getBlock())) { - return; - } - //while it might be nice to check that it's a bed - the block is already air - PlayerBed playerBed = bedWeakCache.getIfPresent(event.getBlock().getLocation()); - if (playerBed == null) { - return; - } - String source = playerBed.player.getName(); - List affected = event.blockList(); - RecordingQueue.addToQueue(ActionFactory.createBlock("bed-explode", playerBed.bed, playerBed.player)); - contructBlockEvent("bed-explode", source, affected); - bedWeakCache.invalidate(event.getBlock().getLocation()); - } else if (event.getBlock().getType() == Material.RESPAWN_ANCHOR) { - if (!Prism.getIgnore().event("respawnanchor-explode", event.getBlock())) { - return; - } - Player player = anchorWeakCache.getIfPresent(event.getBlock().getLocation()); - if (player == null) { - return; - } - String source = player.getName(); - List affected = event.blockList(); - RecordingQueue.addToQueue(ActionFactory.createBlock("respawnanchor-explode", event.getBlock().getState(), player)); - contructBlockEvent("respawnanchor-explode", source, affected); - anchorWeakCache.invalidate(event.getBlock().getLocation()); - } - } - - /** - * Tracks players use respawn anchor to set spawnpoint and cache it in case of explosion. - * @param event PlayerInteractEvent - */ - @EventHandler(priority = EventPriority.MONITOR) - public void onRespawnAnchorUse(PlayerInteractEvent event) { - if (event.hasBlock() && event.getClickedBlock().getType() == Material.RESPAWN_ANCHOR) { - anchorWeakCache.put(event.getClickedBlock().getLocation(), event.getPlayer()); - } - } - - /** - * Tracks players entering a bed and where its not possible cache's it in case of explosion. + * Tracks players entering a bed. * @param enterEvent PlayerBedEnterEvent */ @EventHandler(priority = EventPriority.MONITOR) public void onBedEnter(PlayerBedEnterEvent enterEvent) { - if (enterEvent.getBedEnterResult() == PlayerBedEnterEvent.BedEnterResult.NOT_POSSIBLE_HERE) { - bedWeakCache.put(enterEvent.getBed().getLocation(), new PlayerBed(enterEvent.getPlayer(), - enterEvent.getBed().getState())); - } if (!Prism.getIgnore().event("block-use", enterEvent.getBed())) { return; } diff --git a/Prism/src/main/java/network/darkhelmet/prism/listeners/PrismEntityEvents.java b/Prism/src/main/java/network/darkhelmet/prism/listeners/PrismEntityEvents.java index 1b8fea218..9ce7845a3 100644 --- a/Prism/src/main/java/network/darkhelmet/prism/listeners/PrismEntityEvents.java +++ b/Prism/src/main/java/network/darkhelmet/prism/listeners/PrismEntityEvents.java @@ -14,8 +14,6 @@ import org.bukkit.block.Block; import org.bukkit.block.BlockState; import org.bukkit.entity.ArmorStand; -import org.bukkit.entity.Creeper; -import org.bukkit.entity.EnderDragon; import org.bukkit.entity.Enderman; import org.bukkit.entity.Entity; import org.bukkit.entity.EntityType; @@ -24,11 +22,11 @@ import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Player; import org.bukkit.entity.Projectile; -import org.bukkit.entity.TNTPrimed; import org.bukkit.entity.Wither; import org.bukkit.entity.minecart.PoweredMinecart; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; import org.bukkit.event.block.EntityBlockFormEvent; import org.bukkit.event.entity.CreatureSpawnEvent; import org.bukkit.event.entity.EntityBreakDoorEvent; @@ -37,7 +35,6 @@ import org.bukkit.event.entity.EntityDamageByEntityEvent; import org.bukkit.event.entity.EntityDamageEvent; import org.bukkit.event.entity.EntityDeathEvent; -import org.bukkit.event.entity.EntityExplodeEvent; import org.bukkit.event.entity.EntityTargetEvent; import org.bukkit.event.entity.EntityUnleashEvent; import org.bukkit.event.entity.PlayerLeashEntityEvent; @@ -64,14 +61,16 @@ import java.util.Objects; import java.util.UUID; -public class PrismEntityEvents extends BaseListener { +public class PrismEntityEvents implements Listener { + + private final Prism plugin; /** * Constructor. - * @param plugin Plugin + * @param plugin Prism */ public PrismEntityEvents(Prism plugin) { - super(plugin); + this.plugin = plugin; } /** @@ -763,80 +762,4 @@ public void onEntityBlockForm(final EntityBlockFormEvent event) { block.getBlockData(), newState, entity)); } } - - /** - * EntityExplodeEvent. - * @param event EntityExplodeEvent - */ - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - public void onEntityExplodeChangeBlock(final EntityExplodeEvent event) { - - if (event.blockList().isEmpty()) { - return; - } - String name; - String action = "entity-explode"; - if (event.getEntity() != null) { - if (event.getEntity() instanceof Creeper) { - if (!Prism.getIgnore().event("creeper-explode", event.getEntity().getWorld())) { - return; - } - action = "creeper-explode"; - name = "creeper"; - } else if (event.getEntity() instanceof TNTPrimed) { - if (!Prism.getIgnore().event("tnt-explode", event.getEntity().getWorld())) { - return; - } - action = "tnt-explode"; - Entity source = ((TNTPrimed) event.getEntity()).getSource(); - name = followTntTrail(source); - } else if (event.getEntity() instanceof EnderDragon) { - if (!Prism.getIgnore().event("dragon-eat", event.getEntity().getWorld())) { - return; - } - action = "dragon-eat"; - name = "enderdragon"; - } else { - if (!Prism.getIgnore().event("entity-explode", event.getLocation().getWorld())) { - return; - } - try { - name = event.getEntity().getType().name().toLowerCase().replace("_", " "); - name = name.length() > 15 ? name.substring(0, 15) : name; // I - } catch (final NullPointerException e) { - name = "unknown"; - } - } - } else { - if (!Prism.getIgnore().event("entity-explode", event.getLocation().getWorld())) { - return; - } - name = "magic"; - } - contructBlockEvent(action,name,event.blockList()); - } - - private String followTntTrail(Entity initial) { - int counter = 10000000; - - while (initial != null) { - if (initial instanceof Player) { - return initial.getName(); - } else if (initial instanceof TNTPrimed) { - initial = (((TNTPrimed) initial).getSource()); - if (counter < 0 && initial != null) { - Location last = initial.getLocation(); - plugin.getLogger().warning("TnT chain has exceeded one million, will not continue!"); - plugin.getLogger().warning("Last Tnt was at " + last.getX() + ", " + last.getY() + ". " - + last.getZ() + " in world " + last.getWorld()); - return "tnt"; - } - counter--; - } else { - return initial.getType().name(); - } - } - - return "tnt"; - } } diff --git a/Prism/src/main/java/network/darkhelmet/prism/listeners/PrismExplodeEvents.java b/Prism/src/main/java/network/darkhelmet/prism/listeners/PrismExplodeEvents.java new file mode 100644 index 000000000..622e9f6e0 --- /dev/null +++ b/Prism/src/main/java/network/darkhelmet/prism/listeners/PrismExplodeEvents.java @@ -0,0 +1,552 @@ +package network.darkhelmet.prism.listeners; + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import network.darkhelmet.prism.Prism; +import network.darkhelmet.prism.actionlibs.ActionFactory; +import network.darkhelmet.prism.actionlibs.RecordingQueue; +import network.darkhelmet.prism.utils.MaterialTag; +import network.darkhelmet.prism.utils.block.Utilities; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.data.Bisected; +import org.bukkit.block.data.type.Bed; +import org.bukkit.block.data.type.Door; +import org.bukkit.block.data.type.RespawnAnchor; +import org.bukkit.entity.Creeper; +import org.bukkit.entity.EnderCrystal; +import org.bukkit.entity.EnderDragon; +import org.bukkit.entity.Entity; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Mob; +import org.bukkit.entity.Player; +import org.bukkit.entity.Projectile; +import org.bukkit.entity.TNTPrimed; +import org.bukkit.entity.minecart.ExplosiveMinecart; +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.block.BlockExplodeEvent; +import org.bukkit.event.block.BlockIgniteEvent; +import org.bukkit.event.block.BlockPlaceEvent; +import org.bukkit.event.block.BlockRedstoneEvent; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.EntityDamageEvent; +import org.bukkit.event.entity.EntityExplodeEvent; +import org.bukkit.event.entity.EntitySpawnEvent; +import org.bukkit.event.entity.ProjectileHitEvent; +import org.bukkit.event.entity.ProjectileLaunchEvent; +import org.bukkit.event.player.PlayerInteractEntityEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.projectiles.BlockProjectileSource; +import org.bukkit.projectiles.ProjectileSource; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.concurrent.TimeUnit; + +public class PrismExplodeEvents implements Listener { + + private final Prism plugin; + + /** + * Constructor. + * @param plugin Prism + */ + public PrismExplodeEvents(Prism plugin) { + this.plugin = plugin; + } + + private final Cache> weakCache = CacheBuilder + .newBuilder() + .expireAfterAccess(10, TimeUnit.MINUTES) + .build(); + + + /* + ============================= + ## Cache Handler ## + ============================= + */ + + public void addCache(Object obj, String action, String actor) { + addCache(obj, null, action, actor, null); + } + + public void addCache(Object obj, Object old, String action, String actor) { + addCache(obj, old, action, actor, null); + } + + public void addCache(Object obj, Object old, String action, String actor, Material blockType) { + ArrayList causes; + if (old != null) { + List oldCache = getCache(old); + if (oldCache != null) { + causes = new ArrayList<>(oldCache); + } else { + causes = new ArrayList<>(); + } + } else { + causes = new ArrayList<>(); + } + + if (action != null) { + causes.add(0, new Cause(action, actor, blockType)); + } + weakCache.put(obj, causes); + } + + public List getCache(Object obj) { + return weakCache.getIfPresent(obj); + } + + + /* + ============================ + ## Explode Reason Track ## + ============================ + */ + + @EventHandler(priority = EventPriority.MONITOR) + public void onInteractCreeper(PlayerInteractEntityEvent e) { + Entity clicked = e.getRightClicked(); + if (clicked instanceof Creeper) { + ItemStack mainHand = e.getPlayer().getInventory().getItemInMainHand(); + ItemStack offHand = e.getPlayer().getInventory().getItemInOffHand(); + if ((mainHand != null && mainHand.getType() == Material.FLINT_AND_STEEL) + || (offHand != null && offHand.getType() == Material.FLINT_AND_STEEL)) { + addCache(clicked, "igniting", e.getPlayer().getName()); + } + } + } + + @EventHandler(priority = EventPriority.MONITOR) + public void onPlayerInteract(PlayerInteractEvent e) { + if (e.getAction() != Action.RIGHT_CLICK_BLOCK) { + return; + } + Block clickedBlock = e.getClickedBlock(); + Location location = clickedBlock.getLocation(); + Material material = clickedBlock.getBlockData().getMaterial(); + if (clickedBlock.getBlockData() instanceof Bed) { + Bed bed = (Bed) clickedBlock.getBlockData(); + Location subtract = location.clone().subtract(bed.getFacing().getDirection()); + if (bed.getPart() == Bed.Part.FOOT) { + location.add(bed.getFacing().getDirection()); + } + addCache(location, null, "clicking", e.getPlayer().getName(), material); + addCache(subtract, null, "clicking", e.getPlayer().getName(), material); + } + if (clickedBlock.getBlockData() instanceof RespawnAnchor) { + addCache(clickedBlock.getLocation(), null, "clicking", e.getPlayer().getName(), material); + } + } + + @EventHandler(priority = EventPriority.MONITOR) + public void onProjectileLaunch(ProjectileLaunchEvent e) { + Projectile entity = e.getEntity(); + ProjectileSource shooter = entity.getShooter(); + String projName = entity.getName().toLowerCase(Locale.ROOT).replace("_", " "); + if (shooter == null) { + // Nullable + addCache(entity, "shooting " + projName, null); + return; + } + + if (shooter instanceof Entity) { + if (shooter instanceof Mob && ((Mob) shooter).getTarget() != null) { + // Ghast or something, attacking player or others. + addCache(shooter, "combat " + ((Mob) shooter).getName().toLowerCase(Locale.ROOT) + " shooting " + projName, ((Mob) shooter).getTarget().getName()); + addCache(entity, "combat " + ((Mob) shooter).getName().toLowerCase(Locale.ROOT) + " shooting " + projName, ((Mob) shooter).getTarget().getName()); + return; + } + // Mostly players. + addCache(shooter, "shooting " + projName, ((Entity) shooter).getName()); + addCache(entity, "shooting " + projName, ((Entity) shooter).getName()); + + } else if (shooter instanceof BlockProjectileSource) { + // Like dispenser with fireball..... + addCache(shooter, "shooting " + projName, ((BlockProjectileSource) shooter).getBlock().getType().name()); + addCache(entity, "shooting " + projName, ((BlockProjectileSource) shooter).getBlock().getType().name()); + } else { + // If not listed here... + addCache(shooter, "shooting " + projName, shooter.getClass().getName()); + addCache(entity, "shooting " + projName, shooter.getClass().getName()); + } + } + + @EventHandler(priority = EventPriority.MONITOR) + public void onPrimedTNTSpawn(EntitySpawnEvent e) { + Entity entity = e.getEntity(); + if (!(entity instanceof TNTPrimed)) + return; + TNTPrimed tntPrimed = (TNTPrimed) entity; + Entity source = tntPrimed.getSource(); + // TODO: Null if being shot by dispenser with fireball, how to deal with? + if (source != null) { + if (getCache(source) != null) { + addCache(entity, source, null, null); + return; + } + if (source.getType() == EntityType.PLAYER) { + // Don't return because it's possible caused by other exploded blocks by the player. + addCache(entity, null, "igniting", source.getName()); + } + } + + Location blockCorner = entity.getLocation().clone().subtract(0.5, 0, 0.5); + Location nearby = null; + Location self = null; + for (Object key : weakCache.asMap().keySet()) { + if (key instanceof Location) { + Location loc = (Location) key; + if (loc.getWorld().equals(blockCorner.getWorld())) { + if (loc.distance(blockCorner) < 0.5) { + // Self is mostly tnt, useless. + self = loc; + } else if (loc.distance(blockCorner) < 1.5) { + // Can be redstone or something nearby + nearby = loc; + } + } + } + } + if (nearby != null) { + addCache(entity, nearby, null, null); + return; + } + if (self != null) { + addCache(entity, self, null, null); + } + } + @EventHandler(priority = EventPriority.MONITOR) + public void onBlockPlace(BlockPlaceEvent event) { + Block block = event.getBlock(); + addCache(block.getLocation(), + "placing " + block.getType().name().toLowerCase(Locale.ROOT).replace("_", " "), + event.getPlayer().getName()); + } + + @EventHandler(priority = EventPriority.LOWEST) + public void onHitEndCrystal(EntityDamageByEntityEvent e) { + if (!(e.getEntity() instanceof EnderCrystal)) + return; + if (e.getDamager() instanceof Player) { + addCache(e.getEntity(), "hiting", e.getDamager().getName()); + } else { + if (getCache(e.getDamager()) != null) { + addCache(e.getEntity(), e.getDamager(), null, null); + } else if (e.getDamager() instanceof Projectile) { + Projectile projectile = (Projectile) e.getDamager(); + if (projectile.getShooter() != null && projectile.getShooter() instanceof Player) { + addCache(e.getEntity(), "shooting", ((Player) projectile.getShooter()).getName()); + } + } + } + } + + @EventHandler(priority = EventPriority.MONITOR) + public void onBlockIgnite(BlockIgniteEvent e) { + Location location = e.getBlock().getLocation(); + + Entity entity = e.getIgnitingEntity(); + if (entity != null) { + if (entity.getType() == EntityType.PLAYER) { + addCache(location, "igniting", e.getPlayer().getName()); + + } else if (getCache(entity) != null) { + addCache(location, entity, null, null); + + } else if (entity instanceof Projectile) { + if (((Projectile) entity).getShooter() != null) { + ProjectileSource shooter = ((Projectile) entity).getShooter(); + if (shooter instanceof Player) { + addCache(location, "shooting", ((Player) shooter).getName()); + } + } + } + + } else if (e.getIgnitingBlock() != null) { + if (getCache(e.getIgnitingBlock().getLocation()) != null) { + addCache(location, e.getIgnitingBlock().getLocation(), null, null); + } + } + } + + @EventHandler(priority = EventPriority.LOWEST) + public void onProjectileHit(ProjectileHitEvent e) { + if (e.getHitEntity() instanceof ExplosiveMinecart || e.getEntityType() == EntityType.ENDER_CRYSTAL) { + if (e.getEntity().getShooter() != null && e.getEntity().getShooter() instanceof Player) { + if (getCache(e.getEntity()) != null) { + addCache(e.getHitEntity(), e.getEntity(), null, null); + } else { + if (e.getEntity().getShooter() != null && e.getEntity().getShooter() instanceof Player) { + addCache(e.getHitEntity(), null, ((Player) e.getEntity().getShooter()).getName()); + } + } + } + } + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void onBlockRedstone(BlockRedstoneEvent e) { + // TODO waste ram and need to detect the player. + addCache(e.getBlock().getLocation(), "redstone", null); + } + + + /* + ============================ + ## Explode Events Track ## + ============================ + */ + + @EventHandler(priority = EventPriority.MONITOR) + public void onBlockExplode(BlockExplodeEvent e) { + Block block = e.getBlock(); + Location location = block.getLocation(); + boolean blockTrack = true; + List causes = getCache(block); + if (causes == null) { + blockTrack = false; + causes = getCache(location); + } + final String action; + + final Material blockType; + if (causes == null) { + if (!Prism.getIgnore().event("block-explode", block)) { + return; + } + blockType = null; + action = "block-explode"; + } else { + blockType = causes.get(0).blockType; + if (MaterialTag.BEDS.isTagged(blockType)) { + if (!Prism.getIgnore().event("bed-explode", block)) { + return; + } + action = "bed-explode"; + } else if (blockType == Material.RESPAWN_ANCHOR) { + if (!Prism.getIgnore().event("respawnanchor-explode", block)) { + return; + } + action = "respawnanchor-explode"; + } else { + if (!Prism.getIgnore().event("block-explode", block)) { + return; + } + action = "block-explode"; + } + } + + final String blockName; + final String niceName; + if (blockType == null) { + blockName = null; + niceName = getNiceFullName(null, causes); + } else { + blockName = blockType.name().toLowerCase().replace("_", " "); + niceName = getNiceFullName(blockName, causes); + } + contructExplodeEvent(action, niceName, e.blockList(), blockTrack ? block : location, blockName); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void onEntityExplode(EntityExplodeEvent e) { + List blockList = e.blockList(); + if (blockList.isEmpty()) { + return; + } + Entity entity = e.getEntity(); + List causes = getCache(entity); + + String action = "entity-explode"; + String entityName = null; + String overrideActor = null; + + if (entity == null) { + if (!Prism.getIgnore().event("entity-explode", e.getLocation().getWorld())) { + return; + } + } else if (entity instanceof TNTPrimed) { + if (!Prism.getIgnore().event("tnt-explode", entity.getWorld())) { + return; + } + action = "tnt-explode"; + entityName = "tnt"; + } else if (entity instanceof Creeper) { + if (!Prism.getIgnore().event("creeper-explode", entity.getWorld())) { + return; + } + if (causes == null) { + overrideActor = "creeper < combat (" + ((Creeper) entity).getTarget().getName() + ")"; + } + action = "creeper-explode"; + } else if (entity instanceof EnderDragon) { + if (!Prism.getIgnore().event("dragon-eat", entity.getWorld())) { + return; + } + action = "dragon-eat"; + entityName = "enderdragon"; + } else if (entity instanceof EnderCrystal) { + // TODO if we need a action for this + if (!Prism.getIgnore().event("entity-explode", e.getEntity().getWorld())) { + return; + } + } else if (entity instanceof ExplosiveMinecart) { + if (!Prism.getIgnore().event("entity-explode", e.getEntity().getWorld())) { + return; + } + Location blockCorner = entity.getLocation().clone().subtract(0.5, 0, 0.5); + Location nearby = null; + Location rail = null; + for (Object key : weakCache.asMap().keySet()) { + if (key instanceof Location) { + Location loc = (Location) key; + if (loc.getWorld().equals(blockCorner.getWorld())) { + if (loc.distance(blockCorner) < 0.5) { + if (MaterialTag.RAILS.isTagged(loc.getBlock().getType())) { + // Rail is useless for getting who make it explode if using redstone. + rail = loc; + continue; + } + contructExplodeEvent(action, getNiceFullName("tntminecart", getCache(key)), e.blockList(), entity, "tntminecart"); + return; + } else if (loc.distance(blockCorner) < 1.5) { + // Can be redstone or something nearby + nearby = loc; + } + } + } + } + if (nearby != null) { + contructExplodeEvent(action, getNiceFullName("tntminecart", getCache(nearby)), e.blockList(), entity, "tntminecart"); + return; + } else if (rail != null) { + contructExplodeEvent(action, getNiceFullName("tntminecart", getCache(rail)), e.blockList(), entity, "tntminecart"); + return; + } + + entityName = "tntminecart"; + } else { + if (!Prism.getIgnore().event("entity-explode", e.getLocation().getWorld())) { + return; + } + } + + if (entityName == null) { + if (entity == null) { + entityName = "magic"; + } else { + entityName = entity.getName().toLowerCase(Locale.ROOT); + } + } + + // Last damage is the cause + if (causes == null) { + EntityDamageEvent lastDamageCause = entity.getLastDamageCause(); + if (lastDamageCause instanceof EntityDamageByEntityEvent) { + addCache(entity, "combat", ((EntityDamageByEntityEvent) lastDamageCause).getDamager().getName()); + causes = getCache(entity); + } + } + + String fullActor; + if (overrideActor == null) { + fullActor = getNiceFullName(entityName, causes); + } else { + fullActor = overrideActor; + } + + contructExplodeEvent(action, fullActor, e.blockList(), entity, entityName); + weakCache.invalidate(entity); + } + + private String getNiceFullName(String head, List causes) { + if (causes == null) { + return "unknown"; + } + StringBuilder builder = new StringBuilder(); + String lastAction = head; + if (head != null) { + builder.append(" < ").append(head); + } + byte count = 1; + for (Cause cause : causes) { + if (cause.action.equals(lastAction)) { + count++; + } else { + if (count > 1) { + builder.append(" x").append(count); + } + builder.append(" < "); + count = 1; + lastAction = cause.action; + builder.append(cause.action); + if (cause.actor != null) { + builder.append(" (").append(cause.actor).append(')'); + } + } + } + if (count > 1) { + builder.append(" x").append(count); + } + return builder.substring(3, builder.length()); + } + + protected void contructExplodeEvent(final String parentAction, final String cause, final List blockList, final Object causeObj, final String action) { + final PrismBlockEvents be = new PrismBlockEvents(plugin); //todo is this necessary? + for (Block block : blockList) { + if (causeObj != null) { + addCache(block, causeObj, action, null); + addCache(block.getLocation(), causeObj, action, null); + } + // don't bother record upper doors. + if (MaterialTag.DOORS.isTagged(block.getType()) + && ((Door) block.getState().getBlockData()).getHalf() == Bisected.Half.TOP) { + continue; + } + + // Change handling a bit if it's a long block + final Block sibling = Utilities.getSiblingForDoubleLengthBlock(block); + if (sibling != null && !block.getType().equals(Material.CHEST) + && !block.getType().equals(Material.TRAPPED_CHEST)) { + block = sibling; + } + + // log items removed from container + // note: done before the container so a "rewind" for rollback will + // work properly + final Block b2 = block; + be.forEachItem(block, (i, s) -> RecordingQueue.addToQueue(ActionFactory.createItemStack("item-remove", + i, i.getAmount(), 0, null, b2.getLocation(), cause))); + // be.logItemRemoveFromDestroyedContainer( name, block ); + RecordingQueue.addToQueue(ActionFactory.createBlock(parentAction, block, cause)); + // look for relationships + be.logBlockRelationshipsForBlock(cause, block); + + } + } + + public static class Cause { + + @NotNull final String action; + @Nullable final String actor; + @Nullable final Material blockType; + + Cause(String action, String actor, Material blockType) { + this.action = action; + this.actor = actor; + this.blockType = blockType; + } + + } + +}