/**
Copyright (C) <2015> <coolAlias>
This file is part of coolAlias' Zelda Sword Skills Minecraft Mod; as such,
you can redistribute it and/or modify it under the terms of the GNU
General Public License as published by the Free Software Foundation,
either version 3 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package zeldaswordskills.entity.projectile;
import java.util.HashSet;
import java.util.Set;
import net.minecraft.block.Block;
import net.minecraft.block.material.Material;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.boss.IBossDisplayData;
import net.minecraft.entity.monster.EntityEnderman;
import net.minecraft.entity.monster.EntitySkeleton;
import net.minecraft.init.Blocks;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.util.BlockPos;
import net.minecraft.util.DamageSource;
import net.minecraft.util.EnumParticleTypes;
import net.minecraft.util.MathHelper;
import net.minecraft.util.MovingObjectPosition;
import net.minecraft.world.World;
import zeldaswordskills.api.damage.DamageUtils.DamageSourceBaseDirect;
import zeldaswordskills.api.damage.DamageUtils.DamageSourceBaseIndirect;
import zeldaswordskills.api.damage.DamageUtils.DamageSourceFireIndirect;
import zeldaswordskills.api.damage.DamageUtils.DamageSourceIceIndirect;
import zeldaswordskills.api.damage.EnumDamageType;
import zeldaswordskills.api.entity.IEntityEvil;
import zeldaswordskills.entity.ZSSEntityInfo;
import zeldaswordskills.ref.Config;
import zeldaswordskills.ref.Sounds;
import zeldaswordskills.util.WorldUtils;
/**
*
* Fire Arrow
* Ignites nearby blocks and enemies, and melts even the coldest of ice
*
* Ice Arrow
* Freezes water and enemies, as well as turning lava to stone or obsidian, even in the Nether
* Inflicts double damage on fire-based enemies. Be warned that freezing creepers does
* not prevent them from exploding.
*
* Light Arrow
* Ignores armor.
* Double damage against undead, quad damage against the Wither
* Slays non-boss Endermen and Wither Skeletons instantly, regardless of health
* Can also dispel certain magical barriers and pierce through any block,
* making activating buttons particularly tricky, but possible through walls
*
*/
public class EntityArrowElemental extends EntityArrowCustom
{
/** Valid element types for elemental arrows */
public static enum ElementType {
FIRE("fire", EnumParticleTypes.FLAME),
ICE("ice", EnumParticleTypes.CRIT_MAGIC),
LIGHT("light", EnumParticleTypes.EXPLOSION_NORMAL);
public final String unlocalizedName;
public final EnumParticleTypes particle;
private ElementType(String unlocalizedName, EnumParticleTypes particle) {
this.unlocalizedName = unlocalizedName;
this.particle = particle;
}
};
/** Watchable object index for arrow's element type */
private static final int ARROWTYPE_DATAWATCHER_INDEX = 25;
public EntityArrowElemental(World world) {
super(world);
}
public EntityArrowElemental(World world, double x, double y, double z) {
super(world, x, y, z);
}
public EntityArrowElemental(World world, EntityLivingBase shooter, float velocity) {
super(world, shooter, velocity);
}
public EntityArrowElemental(World world, EntityLivingBase shooter, EntityLivingBase target, float velocity, float wobble) {
super(world, shooter, target, velocity, wobble);
}
@Override
public void entityInit() {
super.entityInit();
setDamage(4.0F); // compensate for lower velocity
dataWatcher.addObject(ARROWTYPE_DATAWATCHER_INDEX, ElementType.FIRE.ordinal());
}
/**
* Returns the {@link ElementType} of this arrow
*/
public ElementType getType() {
return ElementType.values()[dataWatcher.getWatchableObjectInt(ARROWTYPE_DATAWATCHER_INDEX)];
}
/**
* Sets this arrow's {@link ElementType}
*/
public EntityArrowElemental setType(ElementType type) {
dataWatcher.updateObject(ARROWTYPE_DATAWATCHER_INDEX, type.ordinal());
if (type == ElementType.FIRE) { setFire(100); }
return this;
}
@Override
protected float getVelocityFactor() {
return 1.3F;
}
@Override
protected DamageSource getDamageSource(Entity entity) {
switch(getType()) {
case FIRE: return new DamageSourceFireIndirect("arrow.fire", this, shootingEntity).setProjectile().setMagicDamage();
case ICE: return new DamageSourceIceIndirect("arrow.ice", this, shootingEntity, 50, 1).setProjectile().setMagicDamage();
case LIGHT: return (entity instanceof EntityEnderman
? new DamageSourceBaseDirect("arrow.light", (shootingEntity != null ? shootingEntity : this), EnumDamageType.HOLY).setProjectile().setMagicDamage().setDamageBypassesArmor()
: new DamageSourceBaseIndirect("arrow.light", this, shootingEntity, EnumDamageType.HOLY).setProjectile().setMagicDamage().setDamageBypassesArmor());
}
return super.getDamageSource(entity);
}
@Override
protected boolean canTargetEntity(Entity entity) {
return getType() == ElementType.LIGHT || super.canTargetEntity(entity);
}
@Override
protected EnumParticleTypes getParticle() {
return getType().particle;
}
@Override
protected boolean shouldSpawnParticles() {
return true;
}
@Override
protected void updateInAir() {
super.updateInAir();
boolean flag = (getType() == ElementType.FIRE && worldObj.handleMaterialAcceleration(getEntityBoundingBox(), Material.water, this));
if (!worldObj.isRemote && getType() == ElementType.ICE &&
(worldObj.handleMaterialAcceleration(getEntityBoundingBox(), Material.water, this) ||
worldObj.handleMaterialAcceleration(getEntityBoundingBox(), Material.lava, this))) {
flag = affectBlocks();
}
if (flag) {
if (getType() == ElementType.FIRE) {
worldObj.playSoundEffect(posX, posY, posZ, Sounds.FIRE_FIZZ, 1.0F, rand.nextFloat() * 0.4F + 0.8F);
}
if (!worldObj.isRemote) {
setDead();
}
}
}
@Override
protected void onImpactBlock(MovingObjectPosition mop) {
boolean flag = (getType() == ElementType.LIGHT && !Config.enableLightArrowNoClip());
if (getType() != ElementType.LIGHT || flag) {
super.onImpactBlock(mop);
if (!worldObj.isRemote && affectBlocks()) {
setDead();
}
if (flag) {
extinguishLightArrow();
}
} else {
if (ticksExisted < 25) {
Block block = worldObj.getBlockState(mop.getBlockPos()).getBlock();
if (block.getMaterial() != Material.air) {
block.onEntityCollidedWithBlock(worldObj, mop.getBlockPos(), this);
}
} else {
extinguishLightArrow();
}
}
}
@Override
protected void onImpactEntity(MovingObjectPosition mop) {
if (getType() == ElementType.LIGHT && mop.entityHit instanceof EntityLivingBase && canOneHitKill(mop.entityHit)) {
float velocity = MathHelper.sqrt_double(motionX * motionX + motionY * motionY + motionZ * motionZ);
EntityLivingBase entity = (EntityLivingBase) mop.entityHit;
entity.attackEntityFrom(getDamageSource(entity), entity.getMaxHealth() * 0.425F * velocity);
playSound(Sounds.BOW_HIT, 1.0F, 1.2F / (rand.nextFloat() * 0.2F + 0.9F));
// TODO render bright flash, different sound effect?
if (!worldObj.isRemote) {
setDead();
}
} else {
super.onImpactEntity(mop);
}
}
@Override
protected float calculateDamage(Entity entityHit) {
float dmg = super.calculateDamage(entityHit);
if (getType() == ElementType.LIGHT && entityHit instanceof IEntityEvil) {
dmg = ((IEntityEvil) entityHit).getLightArrowDamage(dmg);
}
return dmg;
}
@Override
protected void handlePostDamageEffects(EntityLivingBase entity) {
super.handlePostDamageEffects(entity);
if (!entity.isDead && getType() == ElementType.ICE) {
ZSSEntityInfo.get(entity).stun(MathHelper.ceiling_float_int(calculateDamage(entity)) * 10, true);
int i = MathHelper.floor_double(entity.posX);
int j = MathHelper.floor_double(entity.posY);
int k = MathHelper.floor_double(entity.posZ);
worldObj.setBlockState(new BlockPos(i, j, k), Blocks.ice.getDefaultState());
worldObj.setBlockState(new BlockPos(i, j + 1, k), Blocks.ice.getDefaultState());
worldObj.playSoundEffect(i + 0.5D, j + 0.5D, k + 0.5D, Sounds.GLASS_BREAK, 1.0F, rand.nextFloat() * 0.4F + 0.8F);
}
}
/**
* Returns true if the light arrow can kill this entity in one hit (endermen and wither skeletons)
*/
private boolean canOneHitKill(Entity entity) {
if (entity instanceof IEntityEvil) {
return ((IEntityEvil) entity).isLightArrowFatal();
}
boolean flag = (entity instanceof EntitySkeleton && ((EntitySkeleton) entity).getSkeletonType() == 1);
return (!(entity instanceof IBossDisplayData)) && (flag || entity instanceof EntityEnderman);
}
/**
* Affects all blocks within AoE; returns true if arrow should be consumed
*/
protected boolean affectBlocks() {
boolean flag = false;
Set<BlockPos> affectedBlocks = new HashSet<BlockPos>(WorldUtils.getAffectedBlocksList(worldObj, rand, 1.5F, posX, posY, posZ, null));
Block block;
for (BlockPos pos : affectedBlocks) {
block = worldObj.getBlockState(pos).getBlock();
switch(getType()) {
case FIRE:
if (block.getMaterial() == Material.air && Config.enableFireArrowIgnite()) {
Block block2 = worldObj.getBlockState(pos.down()).getBlock();
if (block2.isFullBlock() && rand.nextInt(8) == 0) {
worldObj.playSoundEffect(pos.getX() + 0.5D, pos.getY() + 0.5D, pos.getZ() + 0.5D, Sounds.FIRE_IGNITE, 1.0F, rand.nextFloat() * 0.4F + 0.8F);
worldObj.setBlockState(pos, Blocks.fire.getDefaultState());
flag = true;
}
} else if (WorldUtils.canMeltBlock(worldObj, block, pos)) {
worldObj.playSoundEffect(pos.getX() + 0.5D, pos.getY() + 0.5D, pos.getZ() + 0.5D, Sounds.FIRE_FIZZ, 1.0F, rand.nextFloat() * 0.4F + 0.8F);
worldObj.setBlockToAir(pos);
flag = true;
}
break;
case ICE:
if (block.getMaterial() == Material.water) {
worldObj.playSoundEffect(pos.getX() + 0.5D, pos.getY() + 0.5D, pos.getZ() + 0.5D, Sounds.GLASS_BREAK, 1.0F, rand.nextFloat() * 0.4F + 0.8F);
worldObj.setBlockState(pos, Blocks.ice.getDefaultState());
flag = true;
} else if (block.getMaterial() == Material.lava) {
worldObj.playSoundEffect(pos.getX() + 0.5D, pos.getY() + 0.5D, pos.getZ() + 0.5D, Sounds.FIRE_FIZZ, 1.0F, rand.nextFloat() * 0.4F + 0.8F);
worldObj.setBlockState(pos, (block == Blocks.lava ? Blocks.obsidian : Blocks.cobblestone).getDefaultState());
flag = true;
} else if (block.getMaterial() == Material.fire) {
worldObj.playSoundEffect(pos.getX() + 0.5D, pos.getY() + 0.5D, pos.getZ() + 0.5D, Sounds.FIRE_FIZZ, 1.0F, rand.nextFloat() * 0.4F + 0.8F);
worldObj.setBlockToAir(pos);
flag = true;
}
break;
case LIGHT:
// TODO dispel magical barriers
break;
}
}
return flag;
}
/**
* Sets this arrow to dead after spawning some particles
*/
private void extinguishLightArrow() {
for (int i = 0; i < 10; ++i) {
double d0 = rand.nextGaussian() * 0.02D;
double d1 = rand.nextGaussian() * 0.02D;
double d2 = rand.nextGaussian() * 0.02D;
worldObj.spawnParticle(EnumParticleTypes.EXPLOSION_NORMAL, posX + (double)(rand.nextFloat() * width * 2.0F) - (double) width,
posY + (double)(rand.nextFloat() * height), posZ + (double)(rand.nextFloat() * width * 2.0F) - (double) width, d0, d1, d2);
}
if (!worldObj.isRemote) {
setDead();
}
}
@Override
public void writeEntityToNBT(NBTTagCompound compound) {
super.writeEntityToNBT(compound);
compound.setInteger("arrowType", getType().ordinal());
}
@Override
public void readEntityFromNBT(NBTTagCompound compound) {
super.readEntityFromNBT(compound);
setType(ElementType.values()[compound.getInteger("arrowType") % ElementType.values().length]);
}
}