package tc.oc.pgm.tnt; import java.util.Random; import javax.annotation.Nullable; import javax.inject.Inject; import org.bukkit.Material; import org.bukkit.Sound; import org.bukkit.World; import org.bukkit.block.BlockState; import org.bukkit.block.Dispenser; import org.bukkit.entity.Entity; import org.bukkit.entity.TNTPrimed; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.block.BlockPlaceEvent; import org.bukkit.event.entity.EntityExplodeEvent; import org.bukkit.event.entity.ExplosionPrimeByEntityEvent; import org.bukkit.event.entity.ExplosionPrimeEvent; import org.bukkit.inventory.ItemStack; import org.bukkit.util.Vector; import tc.oc.commons.bukkit.util.BlockUtils; import tc.oc.pgm.events.BlockTransformEvent; import tc.oc.pgm.events.ListenerScope; import tc.oc.pgm.match.Match; import tc.oc.pgm.match.MatchModule; import tc.oc.pgm.match.MatchScope; import static com.google.common.base.Preconditions.checkNotNull; @ListenerScope(MatchScope.RUNNING) public class TNTMatchModule extends MatchModule implements Listener { private final TNTProperties properties; @Inject private TNTMatchModule(TNTProperties properties) { this.properties = properties; } public TNTProperties getProperties() { return properties; } public int getFuseTicks() { assert this.properties.fuse != null; return (int) (this.properties.fuse.toMillis() / 50.0); } private boolean callPrimeEvent(TNTPrimed tnt, @Nullable Entity primer, boolean instant) { final ExplosionPrimeEvent event; if(instant) { event = new InstantTNTPlaceEvent(tnt, checkNotNull(primer)); } else if(primer != null) { event = new ExplosionPrimeByEntityEvent(tnt, primer); } else { event = new ExplosionPrimeEvent(tnt); } getMatch().callEvent(event); if(event.isCancelled()) { tnt.remove(); return false; } else { return true; } } @EventHandler(ignoreCancelled = true) public void yieldSet(EntityExplodeEvent event) { if(this.properties.yield != null && event.getEntity() instanceof TNTPrimed) { event.setYield(this.properties.yield); } } @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void handleInstantActivation(BlockPlaceEvent event) { if(this.properties.instantIgnite && event.getBlock().getType() == Material.TNT) { World world = event.getBlock().getWorld(); TNTPrimed tnt = world.spawn(BlockUtils.base(event.getBlock()), TNTPrimed.class); if(this.properties.fuse != null) { tnt.setFuseTicks(this.getFuseTicks()); } if(this.properties.power != null) { tnt.setYield(this.properties.power); // Note: not related to EntityExplodeEvent.yield } if(callPrimeEvent(tnt, event.getPlayer(), true)) { // Only cancel the block placement if the prime event is NOT cancelled. // If priming is cancelled, the block is allowed to stay (unless some // other handler has already cancelled the place event). event.setCancelled(true); world.playSound(tnt.getLocation(), Sound.ENTITY_TNT_PRIMED, 1, 1); ItemStack inHand = event.getItemInHand(); if(inHand.getAmount() == 1) { inHand = null; } else { inHand.setAmount(inHand.getAmount() - 1); } event.getPlayer().getInventory().setItem(event.getHand(), inHand); } } } @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void setCustomProperties(ExplosionPrimeEvent event) { if(event.getEntity() instanceof TNTPrimed) { TNTPrimed tnt = (TNTPrimed) event.getEntity(); if(this.properties.fuse != null) { tnt.setFuseTicks(this.getFuseTicks()); } if(this.properties.power != null) { tnt.setYield(this.properties.power); // Note: not related to EntityExplodeEvent.yield } } } // Make sure this event handler is called before the one in DispenserTracker that clears the placer @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) public void dispenserNukes(BlockTransformEvent event) { BlockState oldState = event.getOldState(); if(oldState instanceof Dispenser && this.properties.dispenserNukeLimit > 0 && this.properties.dispenserNukeMultiplier > 0 && event.getCause() instanceof EntityExplodeEvent) { EntityExplodeEvent explodeEvent = (EntityExplodeEvent) event.getCause(); Dispenser dispenser = (Dispenser) oldState; int tntLimit = Math.round(this.properties.dispenserNukeLimit / this.properties.dispenserNukeMultiplier); int tntCount = 0; for(ItemStack stack : dispenser.getInventory().contents()) { if(stack != null && stack.getType() == Material.TNT) { int transfer = Math.min(stack.getAmount(), tntLimit - tntCount); if(transfer > 0) { stack.setAmount(stack.getAmount() - transfer); tntCount += transfer; } } } tntCount = (int) Math.ceil(tntCount * this.properties.dispenserNukeMultiplier); for(int i = 0; i < tntCount; i++) { TNTPrimed tnt = this.getMatch().getWorld().spawn(BlockUtils.base(dispenser), TNTPrimed.class); tnt.setFuseTicks(10 + this.getMatch().getRandom().nextInt(10)); // between 0.5 and 1.0 seconds, same as vanilla TNT chaining Random random = this.getMatch().getRandom(); Vector velocity = new Vector(random.nextGaussian(), random.nextGaussian(), random.nextGaussian()); // uniform random direction velocity.normalize().multiply(0.5 + 0.5 * random.nextDouble()); tnt.setVelocity(velocity); callPrimeEvent(tnt, explodeEvent.getEntity(), false); } } } }