| package net.glowstone; |
| |
| import net.glowstone.block.GlowBlock; |
| import net.glowstone.block.ItemTable; |
| import net.glowstone.block.blocktype.BlockTNT; |
| import net.glowstone.entity.GlowEntity; |
| import net.glowstone.entity.GlowHumanEntity; |
| import net.glowstone.entity.GlowLivingEntity; |
| import net.glowstone.entity.GlowPlayer; |
| import net.glowstone.net.message.play.game.ExplosionMessage; |
| import org.bukkit.*; |
| import org.bukkit.block.Block; |
| import org.bukkit.block.BlockFace; |
| import org.bukkit.entity.Entity; |
| import org.bukkit.entity.EntityType; |
| import org.bukkit.entity.LivingEntity; |
| import org.bukkit.event.block.BlockIgniteEvent; |
| import org.bukkit.event.entity.EntityDamageEvent; |
| import org.bukkit.event.entity.EntityExplodeEvent; |
| import org.bukkit.util.BlockVector; |
| import org.bukkit.util.Vector; |
| |
| import java.util.*; |
| |
| public final class Explosion { |
| |
| public static final int POWER_TNT = 4; |
| public static final int POWER_BED = 5; |
| public static final int POWER_CREEPER = 3; |
| public static final int POWER_CHARGED_CREEPER = 6; |
| public static final int POWER_GHAST = 1; |
| public static final int POWER_WITHER_SKULL = 1; |
| public static final int POWER_WITHER_CREATION = 7; |
| public static final int POWER_ENDER_CRYSTAL = 6; |
| |
| private float power; |
| private final Entity source; |
| private final Location location; |
| private final boolean incendiary; |
| private final boolean breakBlocks; |
| private final GlowWorld world; |
| private float yield = 0.3f; |
| |
| private static final Random random = new Random(); |
| private final ItemTable itemTable; |
| |
| /** |
| * Creates a new explosion |
| * @param source The entity causing this explosion |
| * @param world The world this explosion is in |
| * @param x The X location of the explosion |
| * @param y The Y location of the explosion |
| * @param z The Z location of the explosion |
| * @param power The power of the explosion |
| * @param incendiary Whether or not blocks should be set on fire |
| * @param breakBlocks Whether blocks should break through this explosion |
| */ |
| public Explosion(Entity source, GlowWorld world, double x, double y, double z, float power, boolean incendiary, boolean breakBlocks) { |
| this(source, new Location(world, x, y, z), power, incendiary, breakBlocks); |
| } |
| |
| /** |
| * Creates a new explosion |
| * @param source The entity causing this explosion |
| * @param location The location this explosion is occuring at. Must contain a GlowWorld |
| * @param power The power of the explosion |
| * @param incendiary Whether or not blocks should be set on fire |
| * @param breakBlocks Whether blocks should break through this explosion |
| */ |
| public Explosion(Entity source, Location location, float power, boolean incendiary, boolean breakBlocks) { |
| if (!(location.getWorld() instanceof GlowWorld)) { |
| throw new IllegalArgumentException("Supplied location does not have a valid GlowWorld"); |
| } |
| |
| this.source = source; |
| this.location = location.clone(); |
| this.power = power; |
| this.incendiary = incendiary; |
| this.breakBlocks = breakBlocks; |
| this.world = (GlowWorld) location.getWorld(); |
| itemTable = ItemTable.instance(); |
| } |
| |
| public boolean explodeWithEvent() { |
| if (power < 0.1f) |
| return true; |
| |
| Set<BlockVector> droppedBlocks = calculateBlocks(); |
| |
| EntityExplodeEvent event = EventFactory.callEvent(new EntityExplodeEvent(source, location, toBlockList(droppedBlocks), yield)); |
| if (event.isCancelled()) return false; |
| |
| this.yield = event.getYield(); |
| |
| playOutSoundAndParticles(); |
| |
| List<Block> blocks = toBlockList(droppedBlocks); |
| |
| for (Block block : blocks) { |
| handleBlockExplosion((GlowBlock) block); |
| } |
| |
| if (incendiary) { |
| for (Block block : blocks) { |
| setBlockOnFire((GlowBlock) block); |
| } |
| } |
| |
| Collection<GlowPlayer> affectedPlayers = damageEntities(); |
| for (GlowPlayer player : affectedPlayers) { |
| playOutExplosion(player, droppedBlocks); |
| } |
| |
| return true; |
| } |
| |
| /////////////////////////////////////////////////// |
| // Calculate all the dropping blocks |
| |
| private Set<BlockVector> calculateBlocks() { |
| if (!breakBlocks) |
| return new HashSet<>(); |
| |
| Set<BlockVector> blocks = new HashSet<>(); |
| |
| final int value = 16; |
| |
| for (int x = 0; x < value; x++) { |
| for (int y = 0; y < value; y++) { |
| for (int z = 0; z < value; z++) { |
| if (!(x == 0 || x == value - 1 || y == 0 || y == value - 1 || z == 0 || z == value - 1)) { |
| continue; |
| } |
| calculateRay(x, y, z, blocks); |
| } |
| } |
| } |
| |
| return blocks; |
| } |
| |
| private void calculateRay(int ox, int oy, int oz, Collection<BlockVector> result) { |
| double x = ox / 7.5 - 1; |
| double y = oy / 7.5 - 1; |
| double z = oz / 7.5 - 1; |
| Vector direction = new Vector(x, y, z); |
| direction.normalize(); |
| direction.multiply(0.3f); // 0.3 blocks away with each step |
| |
| Location current = location.clone(); |
| |
| float currentPower = calculateStartPower(); |
| |
| while (currentPower > 0) { |
| GlowBlock block = world.getBlockAt(current); |
| |
| if (block.getType() != Material.AIR) { |
| double blastDurability = getBlastDurability(block) / 5d; |
| blastDurability += 0.3F; |
| blastDurability *= 0.3F; |
| currentPower -= blastDurability; |
| |
| if (currentPower > 0) { |
| result.add(new BlockVector(block.getX(), block.getY(), block.getZ())); |
| } |
| } |
| |
| current.add(direction); |
| currentPower -= 0.225f; |
| } |
| } |
| |
| private void handleBlockExplosion(GlowBlock block) { |
| if (block.getType() == Material.AIR) { |
| return; |
| } else if (block.getType() == Material.TNT) { |
| BlockTNT.igniteBlock(block, true); |
| return; |
| } |
| |
| block.breakNaturally(yield); |
| } |
| |
| private float calculateStartPower() { |
| float rand = random.nextFloat(); |
| rand *= 0.6F; // (max - 0.7) |
| rand += 0.7; // min |
| return rand * power; |
| } |
| |
| private double getBlastDurability(GlowBlock block) { |
| // TODO: return the block's blast durability |
| return 2.5; |
| } |
| |
| private List<Block> toBlockList(Collection<BlockVector> locs) { |
| List<Block> blocks = new ArrayList<>(locs.size()); |
| for (BlockVector location : locs) |
| blocks.add(world.getBlockAt(location.getBlockX(), location.getBlockY(), location.getBlockZ())); |
| return blocks; |
| } |
| |
| private void setBlockOnFire(GlowBlock block) { |
| if (random.nextInt(3) != 0) |
| return; |
| |
| Block below = block.getRelative(BlockFace.DOWN); |
| // TODO: check for flammable blocks |
| Material belowType = below.getType(); |
| if (belowType == Material.AIR || belowType == Material.FIRE) return; |
| |
| BlockIgniteEvent event = EventFactory.callEvent(new BlockIgniteEvent(block, BlockIgniteEvent.IgniteCause.EXPLOSION, source)); |
| if (event.isCancelled()) |
| return; |
| |
| block.setType(Material.FIRE); |
| } |
| |
| ///////////////////////////////////////// |
| // Damage entities |
| |
| private Collection<GlowPlayer> damageEntities() { |
| float power = this.power; |
| this.power *= 2f; |
| |
| Collection<GlowPlayer> affectedPlayers = new ArrayList<>(); |
| |
| Collection<GlowLivingEntity> entities = getNearbyEntities(); |
| for (GlowLivingEntity entity : entities) { |
| double disDivPower = distanceTo(entity) / (double) this.power; |
| if (disDivPower > 1.0D) continue; |
| |
| Vector vecDistance = distanceToHead(entity); |
| if (vecDistance.length() == 0.0) continue; |
| |
| vecDistance.normalize(); |
| |
| double basicDamage = calculateDamage(entity, disDivPower); |
| int explosionDamage = (int) ((basicDamage * basicDamage + basicDamage) * 4 * (double) power + 1.0D); |
| |
| if (!(entity instanceof GlowHumanEntity && ((GlowHumanEntity) entity).getGameMode() == GameMode.CREATIVE)) { |
| EntityDamageEvent.DamageCause damageCause; |
| if (source == null || source.getType() == EntityType.PRIMED_TNT) { |
| damageCause = EntityDamageEvent.DamageCause.BLOCK_EXPLOSION; |
| } else { |
| damageCause = EntityDamageEvent.DamageCause.ENTITY_EXPLOSION; |
| } |
| entity.damage(explosionDamage, source, damageCause); |
| } |
| |
| double enchantedDamage = calculateEnchantedDamage(basicDamage, entity); |
| vecDistance.multiply(enchantedDamage); |
| |
| Vector currentVelocity = entity.getVelocity(); |
| currentVelocity.add(vecDistance); |
| entity.setVelocity(currentVelocity); |
| |
| if (entity instanceof GlowPlayer) { |
| affectedPlayers.add((GlowPlayer) entity); |
| } |
| } |
| |
| this.power = power; |
| |
| return affectedPlayers; |
| } |
| |
| private double calculateEnchantedDamage(double basicDamage, GlowLivingEntity entity) { |
| int level = 0; // TODO: calculate explosion protection level of entity's equipment |
| |
| if (level > 0) { |
| float sub = level * 0.15f; |
| double damage = basicDamage * sub; |
| damage = Math.floor(damage); |
| return basicDamage - damage; |
| } |
| |
| return basicDamage; |
| } |
| |
| private double calculateDamage(GlowEntity entity, double disDivPower) { |
| double damage = world.rayTrace(location, entity); |
| return (damage * (1D - disDivPower)); |
| } |
| |
| private Collection<GlowLivingEntity> getNearbyEntities() { |
| // TODO: fetch only necessary entities |
| List<LivingEntity> entities = world.getLivingEntities(); |
| List<GlowLivingEntity> nearbyEntities = new ArrayList<>(); |
| |
| for (LivingEntity entity : entities) { |
| if (distanceTo(entity) / (double) power < 1.) { |
| nearbyEntities.add((GlowLivingEntity) entity); |
| } |
| } |
| |
| return nearbyEntities; |
| } |
| |
| private double distanceTo(LivingEntity entity) { |
| return location.clone().subtract(entity.getLocation()).length(); |
| } |
| |
| private Vector distanceToHead(LivingEntity entity) { |
| return entity.getLocation().clone().subtract(location.clone().subtract(0, entity.getEyeHeight(), 0)).toVector(); |
| } |
| |
| /////////////////////////////////////// |
| // Visualize |
| private void playOutSoundAndParticles() { |
| world.playSound(location, Sound.EXPLODE, 4, (1.0F + (random.nextFloat() - random.nextFloat()) * 0.2F) * 0.7F); |
| |
| if (this.power >= 2.0F && this.breakBlocks) { |
| // send huge explosion |
| world.showParticle(location, Particle.EXPLOSION_HUGE, 0, 0, 0, 0, 0); |
| } else { |
| // send large explosion |
| world.showParticle(location, Particle.EXPLOSION_LARGE, 0, 0, 0, 0, 0); |
| } |
| } |
| |
| private void playOutExplosion(GlowPlayer player, Iterable<BlockVector> blocks) { |
| Collection<ExplosionMessage.Record> records = new ArrayList<>(); |
| |
| Location clientLoc = location.clone(); |
| clientLoc.setX((int) clientLoc.getX()); |
| clientLoc.setY((int) clientLoc.getY()); |
| clientLoc.setZ((int) clientLoc.getZ()); |
| |
| for (BlockVector block : blocks) { |
| byte x = (byte) (block.getBlockX() - clientLoc.getBlockX()); |
| byte y = (byte) (block.getBlockY() - clientLoc.getBlockY()); |
| byte z = (byte) (block.getBlockZ() - clientLoc.getBlockZ()); |
| records.add(new ExplosionMessage.Record(x, y, z)); |
| } |
| |
| Vector velocity = player.getVelocity(); |
| ExplosionMessage message = new ExplosionMessage((float) location.getX(), (float) location.getY(), (float) location.getZ(), |
| 5, |
| (float) velocity.getX(), (float) velocity.getY(), (float) velocity.getZ(), |
| records); |
| |
| player.getSession().send(message); |
| } |
| } |