/**
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.ArrayList;
import java.util.List;
import net.minecraft.block.Block;
import net.minecraft.block.state.IBlockState;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.projectile.EntityThrowable;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.potion.Potion;
import net.minecraft.potion.PotionEffect;
import net.minecraft.util.AxisAlignedBB;
import net.minecraft.util.BlockPos;
import net.minecraft.util.EnumParticleTypes;
import net.minecraft.util.MathHelper;
import net.minecraft.util.MovingObjectPosition;
import net.minecraft.util.MovingObjectPosition.MovingObjectType;
import net.minecraft.world.World;
import org.apache.commons.lang3.ArrayUtils;
import zeldaswordskills.api.damage.DamageUtils;
import zeldaswordskills.ref.Sounds;
import zeldaswordskills.util.TargetUtils;
import zeldaswordskills.util.WorldUtils;
public class EntityLeapingBlow extends EntityThrowable
{
/** Keeps track of entities already affected so they don't get attacked twice */
private List<Integer> affectedEntities = new ArrayList<Integer>();
/** Base damage should be set from player's Leaping Blow skill */
private float damage = 2.0F;
/** Base number of ticks this entity can exist */
private int lifespan = 12;
/** Skill level of swordsman; used in many calculations */
private int level = 0;
/** Whether the swordsman is wielding the master sword */
private boolean isMaster = false;
private static final float BASE_SIZE = 1.0F, HEIGHT = 0.5F;
public EntityLeapingBlow(World world) {
super(world);
this.setSize(BASE_SIZE, HEIGHT);
}
public EntityLeapingBlow(World world, EntityLivingBase thrower) {
super(world, thrower);
this.setSize(BASE_SIZE, HEIGHT);
this.posY = thrower.posY + 0.2D;
this.motionY = 0.0D;
this.setThrowableHeading(motionX, motionY, motionZ, getVelocity(), 1.0F);
}
public EntityLeapingBlow(World world, double x, double y, double z) {
super(world, x, y, z);
this.setSize(BASE_SIZE, HEIGHT);
}
/**
* Each level increases the distance traveled as well as the AoE
* Master version increases weakness effect duration
*/
public EntityLeapingBlow setLevel(int level, boolean isMaster) {
this.level = level;
this.lifespan += level;
this.isMaster = isMaster;
return this;
}
/**
* Sets amount of damage that will be caused onImpact
*/
public EntityLeapingBlow setDamage(float amount) {
this.damage = amount;
return this;
}
/** Max distance (squared) from thrower that damage can still be applied */
private double getRangeSquared() {
return (3.0D + level) * (3.0D + level);
}
/** Duration of weakness effect */
private int getPotionDuration() {
return ((isMaster ? 110 : 50) + (level * 10));
}
/** Returns area within which to search for targets each tick */
private AxisAlignedBB getAoE() {
return getEntityBoundingBox().expand((0.25F * level), 0.0F, (0.25F * level));
}
@Override
public void onUpdate() {
super.onUpdate();
if (inGround || ticksExisted > lifespan) { setDead(); }
if (!worldObj.isRemote) {
List<EntityLivingBase> targets = worldObj.getEntitiesWithinAABB(EntityLivingBase.class, getAoE());
for (EntityLivingBase target : targets) {
if (!affectedEntities.contains(target.getEntityId()) && target != getThrower() && !TargetUtils.isTargetInFrontOf(this, target, 30F)) {
affectedEntities.add(target.getEntityId());
float d = damage;
if (getThrower() != null) {
double d0 = (1.0D - getThrower().getDistanceSqToEntity(target) / getRangeSquared());
d *= (d0 > 1.0D ? 1.0D : d0);
if (d < 0.5D) { return; }
}
if (target.attackEntityFrom(DamageUtils.causeIndirectSwordDamage(this, getThrower()), d)) {
target.addPotionEffect(new PotionEffect(Potion.weakness.id, getPotionDuration()));
}
}
}
}
int x = MathHelper.floor_double(posX);
int y = MathHelper.floor_double(posY - 0.20000000298023224D);
int z = MathHelper.floor_double(posZ);
IBlockState state = worldObj.getBlockState(new BlockPos(x, y, z));
Block block = state.getBlock();
int[] extra = {};
EnumParticleTypes particle = (isMaster ? EnumParticleTypes.CRIT_MAGIC : EnumParticleTypes.CRIT);
if (block.getRenderType() != -1) {
particle = EnumParticleTypes.BLOCK_CRACK;
extra = new int[]{Block.getStateId(state)};
worldObj.spawnParticle(EnumParticleTypes.BLOCK_CRACK, this.posX + ((double)this.rand.nextFloat() - 0.5D) * (double)this.width, this.getEntityBoundingBox().minY + 0.1D, this.posZ + ((double)this.rand.nextFloat() - 0.5D) * (double)this.width, -this.motionX * 4.0D, 1.5D, -this.motionZ * 4.0D, extra);
}
spawnParticles(particle, 4, motionZ, 0.01D, motionX, extra);
spawnParticles(particle, 4, -motionZ, 0.01D, -motionX, extra);
}
/**
* Spawns the designated particle at the current entity's position, with optional
* motion to add to the otherwise random amount
* @param n Number of particles to spawn
* @param dX Motion along X-axis (+/- this.motionZ for lateral motion)
* @param dY Motion along Y-axis
* @param dZ Motion along Z-axis (+/- this.motionX for lateral motion)
* @param extra additional arguments for World#spawnParticle
*/
private void spawnParticles(EnumParticleTypes particle, int n, double dX, double dY, double dZ, int ... extra) {
for (int i = 0; i < n; ++i) {
worldObj.spawnParticle(particle, posX, posY, posZ, dX + rand.nextGaussian(), dY, dZ + rand.nextGaussian(), extra);
}
}
@Override
protected void onImpact(MovingObjectPosition mop) {
if (!worldObj.isRemote) {
if (mop.typeOfHit == MovingObjectType.ENTITY) {
Entity entity = mop.entityHit;
if (entity instanceof EntityLivingBase && !affectedEntities.contains(entity.getEntityId()) && entity != getThrower()) {
affectedEntities.add(entity.getEntityId());
if (entity.attackEntityFrom(DamageUtils.causeIndirectSwordDamage(this, getThrower()), damage)) {
WorldUtils.playSoundAtEntity(entity, Sounds.HURT_FLESH, 0.4F, 0.5F);
if (entity instanceof EntityLivingBase) {
((EntityLivingBase) entity).addPotionEffect(new PotionEffect(Potion.weakness.id, 60));
}
}
}
} else {
Block block = worldObj.getBlockState(mop.getBlockPos()).getBlock();
if (block.getMaterial().blocksMovement()) {
setDead();
}
}
}
}
@Override
protected float getVelocity() {
return 0.5F;
}
@Override
public float getGravityVelocity() {
return 0.0F;
}
@Override
public void writeEntityToNBT(NBTTagCompound compound) {
super.writeEntityToNBT(compound);
compound.setBoolean("isMaster", isMaster);
compound.setFloat("damage", damage);
compound.setInteger("level", level);
compound.setInteger("lifespan", lifespan);
compound.setIntArray("affectedEntities", ArrayUtils.toPrimitive(affectedEntities.toArray(new Integer[affectedEntities.size()])));
}
@Override
public void readEntityFromNBT(NBTTagCompound compound) {
super.readEntityFromNBT(compound);
isMaster = (compound.getBoolean("isMaster"));
damage = compound.getFloat("damage");
level = compound.getInteger("level");
lifespan = compound.getInteger("lifespan");
int[] entities = compound.getIntArray("affectedEntities");
for (int i = 0; i < entities.length; ++i) {
affectedEntities.add(entities[i]);
}
}
}