/**
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 io.netty.buffer.ByteBuf;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import net.minecraft.block.Block;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.Blocks;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.util.AxisAlignedBB;
import net.minecraft.util.BlockPos;
import net.minecraft.util.DamageSource;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.EnumParticleTypes;
import net.minecraft.util.MathHelper;
import net.minecraft.util.MovingObjectPosition;
import net.minecraft.util.MovingObjectPosition.MovingObjectType;
import net.minecraft.util.Vec3;
import net.minecraft.world.World;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
import zeldaswordskills.api.damage.DamageUtils.DamageSourceBaseIndirect;
import zeldaswordskills.api.damage.DamageUtils.DamageSourceFireIndirect;
import zeldaswordskills.api.damage.DamageUtils.DamageSourceIceIndirect;
import zeldaswordskills.api.damage.DamageUtils.DamageSourceShockIndirect;
import zeldaswordskills.api.entity.IReflectable;
import zeldaswordskills.api.entity.MagicType;
import zeldaswordskills.item.ItemMagicRod;
import zeldaswordskills.ref.Sounds;
import zeldaswordskills.util.WorldUtils;
/**
*
* Magic spell projectile entity; rendering is two intersecting cubes with texture
* determined by spell type (i.e. fire, ice, etc.)
*
* Upon impact, the spell will 'explode', damaging all entities within the area of
* effect for the full damage amount.
*
* Set the MagicType and AoE before spawning the entity or the client side will not know about it.
*
*/
public class EntityMagicSpell extends EntityMobThrowable implements IReflectable
{
/** The spell's magic type */
private MagicType type = MagicType.FIRE;
/** Set to false to prevent the spell from affecting blocks */
private boolean canGrief = true;
/** The spell's effect radius also affects the render scale */
private float radius = 2.0F;
/** If true, the damage source will be set to ignore armor */
private boolean bypassesArmor;
/** If not set to positive value, default will return (1.0F - (getArea() / 4.0F)) */
private float reflectChance = -1.0F;
/** Set to false for no trailing particles */
private boolean spawnParticles = true;
public EntityMagicSpell(World world) {
super(world);
setGravityVelocity(0.02F);
}
public EntityMagicSpell(World world, EntityLivingBase entity) {
super(world, entity);
setGravityVelocity(0.02F);
resetSize();
}
public EntityMagicSpell(World world, double x, double y, double z) {
super(world, x, y, z);
setGravityVelocity(0.02F);
resetSize();
}
public EntityMagicSpell(World world, EntityLivingBase shooter, EntityLivingBase target, float velocity, float wobble) {
super(world, shooter, target, velocity, wobble);
setGravityVelocity(0.02F);
resetSize();
}
/** Re-sets the entity size based on the current radius */
private void resetSize() {
float f = (float)(radius / 4.0D);
setSize(f, f);
}
public MagicType getType() {
return type;
}
public EntityMagicSpell setType(MagicType type) {
this.type = type;
return this;
}
/**
* Disables griefing - i.e. no blocks will be affected by this spell
*/
public EntityMagicSpell disableGriefing() {
this.canGrief = false;
return this;
}
/** Makes this spell's damage source ignore armor */
public EntityMagicSpell setDamageBypassesArmor() {
bypassesArmor = true;
return this;
}
/** Returns the spell's effect radius */
public float getArea() {
return radius;
}
/** Sets the spell's area of effect radius */
public EntityMagicSpell setArea(float radius) {
this.radius = radius;
resetSize();
return this;
}
/** Sets the chance of the spell being reflected when blocked with the Mirror Shield */
public EntityMagicSpell setReflectChance(float chance) {
this.reflectChance = chance;
return this;
}
/** Disables trailing particles */
public EntityMagicSpell disableTrailingParticles() {
this.spawnParticles = false;
return this;
}
/**
* Returns a damage source corresponding to the magic type (e.g. fire for fire, etc.)
*/
protected DamageSource getDamageSource() {
DamageSource source = new DamageSourceFireIndirect("blast.fire", this, getThrower(), true).setProjectile().setMagicDamage();
switch(getType()) {
case ICE: source = new DamageSourceIceIndirect("blast.ice", this, getThrower(), 50, 1, true).setStunDamage(60, 10, true).setProjectile().setMagicDamage(); break;
case LIGHTNING: source = new DamageSourceShockIndirect("blast.lightning", this, getThrower(), 50, 1, true).setProjectile().setMagicDamage(); break;
case WIND: source = new DamageSourceBaseIndirect("blast.wind", this, getThrower(), true).setProjectile().setMagicDamage(); break;
default: break; // fire
}
if (bypassesArmor) {
source.setDamageBypassesArmor();
}
return source;
}
@Override
public float getReflectChance(ItemStack mirrorShield, EntityPlayer player, Entity shooter) {
return (reflectChance < 0 ? 1.0F - (getArea() / 4.0F) : reflectChance);
}
@Override
public void onReflected(ItemStack mirrorShield, EntityPlayer player, Entity shooter, Entity oldEntity) {}
@Override
protected float getVelocity() {
return 1.0F;
}
@Override
public void onUpdate() {
super.onUpdate();
if (!isDead) {
// spell should 'impact' liquids as well
Block block = worldObj.getBlockState(new BlockPos(this)).getBlock();
if (block.getMaterial().isLiquid()) {
onImpact(new MovingObjectPosition(new Vec3(posX, posY, posZ), EnumFacing.UP, new BlockPos(this)));
}
}
MagicType type = getType();
if (worldObj.isRemote && spawnParticles) {
EnumParticleTypes particle = type.getTrailingParticle();
boolean flag = type != MagicType.FIRE;
for (int i = 0; i < 4; ++i) {
worldObj.spawnParticle(particle,
posX + motionX * (double) i / 4.0D,
posY + motionY * (double) i / 4.0D,
posZ + motionZ * (double) i / 4.0D,
-motionX * 0.25D, -motionY + (flag ? 0.1D : 0.0D), -motionZ * 0.25D);
}
} else if (ticksExisted % type.getSoundFrequency() == 0) {
worldObj.playSoundAtEntity(this, type.getMovingSound(), type.getSoundVolume(rand), type.getSoundPitch(rand));
}
}
@Override
protected void onImpact(MovingObjectPosition mop) {
double x = (mop.entityHit != null ? mop.entityHit.posX : mop.getBlockPos().getX() + 0.5D);
double y = (mop.entityHit != null ? mop.entityHit.posY : mop.getBlockPos().getY() + 0.5D);
double z = (mop.entityHit != null ? mop.entityHit.posZ : mop.getBlockPos().getZ() + 0.5D);
float r = getArea();
List<EntityLivingBase> list = worldObj.getEntitiesWithinAABB(EntityLivingBase.class, new AxisAlignedBB(x - r, y - r, z - r, x + r, y + r, z + r));
for (EntityLivingBase entity : list) {
Vec3 vec3 = new Vec3(posX - motionX, posY - motionY, posZ - motionZ);
Vec3 vec31 = new Vec3(entity.posX, entity.posY, entity.posZ);
MovingObjectPosition mop1 = worldObj.rayTraceBlocks(vec3, vec31);
if (mop1 != null && mop1.typeOfHit == MovingObjectType.BLOCK) {
Block block = worldObj.getBlockState(mop1.getBlockPos()).getBlock();
if (block.getMaterial().blocksMovement()) {
continue;
}
}
if (entity.attackEntityFrom(getDamageSource(), getDamage()) && !entity.isDead) {
handlePostDamageEffects(entity);
}
}
if (worldObj.isRemote) {
spawnImpactParticles(EnumParticleTypes.EXPLOSION_LARGE, 4, -0.1F);
spawnImpactParticles(getType().getTrailingParticle(), 16, getType() == MagicType.ICE ? 0.0F : -0.2F);
} else {
worldObj.playSoundAtEntity(this, "random.explode", 2.0F, (1.0F + (worldObj.rand.nextFloat() - worldObj.rand.nextFloat()) * 0.2F) * 0.7F);
if (canGrief && getType().affectsBlocks(worldObj, getThrower())) {
Set<BlockPos> affectedBlocks = new HashSet<BlockPos>(WorldUtils.getAffectedBlocksList(worldObj, rand, r, posX, posY, posZ, null));
ItemMagicRod.affectAllBlocks(worldObj, affectedBlocks, getType());
}
setDead();
}
}
@SideOnly(Side.CLIENT)
private void spawnImpactParticles(EnumParticleTypes particle, int n, float offsetY) {
for (int i = 0; i < n; ++i) {
double dx = posX - motionX * (double) i / 4.0D;
double dy = posY - motionY * (double) i / 4.0D;
double dz = posZ - motionX * (double) i / 4.0D;
worldObj.spawnParticle(particle, (dx + rand.nextFloat() - 0.5F),
(dy + rand.nextFloat() - 0.5F),
(dz + rand.nextFloat() - 0.5F), 0.25F * (rand.nextFloat() - 0.5F),
(rand.nextFloat() * 0.25F) + offsetY, 0.25F * (rand.nextFloat() - 0.5F));
}
}
protected void handlePostDamageEffects(EntityLivingBase entity) {
switch(getType()) {
case ICE:
int i = MathHelper.floor_double(entity.posX);
int j = MathHelper.floor_double(entity.posY);
int k = MathHelper.floor_double(entity.posZ);
if (getThrower() instanceof EntityPlayer) {
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);
break;
case FIRE:
if (!entity.isImmuneToFire()) {
entity.setFire((int) Math.ceil(getDamage()));
}
break;
case WIND:
double power = Math.min(3.0D, (getDamage() / 6.0D));
if (power > 0) {
float f3 = MathHelper.sqrt_double(motionX * motionX + motionZ * motionZ);
if (f3 > 0.0F) {
double knockback = power * 0.6000000238418579D / (double) f3;
entity.addVelocity(motionX * knockback, 0.1D, motionZ * knockback);
}
}
break;
default:
}
}
@Override
public void writeEntityToNBT(NBTTagCompound compound) {
super.writeEntityToNBT(compound);
compound.setInteger("magicType", getType().ordinal());
compound.setFloat("areaOfEffect", getArea());
compound.setFloat("reflectChance", reflectChance);
compound.setBoolean("bypassesArmor", bypassesArmor);
compound.setBoolean("canGrief", canGrief);
compound.setBoolean("spawnParticles", spawnParticles);
}
@Override
public void readEntityFromNBT(NBTTagCompound compound) {
super.readEntityFromNBT(compound);
setType(MagicType.values()[compound.getInteger("magicType") % MagicType.values().length]);
setArea(compound.getFloat("areaOfEffect"));
reflectChance = compound.getFloat("reflectChance");
bypassesArmor = compound.getBoolean("bypassesArmor");
canGrief = compound.getBoolean("canGrief");
spawnParticles = compound.getBoolean("spawnParticles");
}
@Override
public void writeSpawnData(ByteBuf buffer) {
super.writeSpawnData(buffer);
buffer.writeInt(type.ordinal());
buffer.writeFloat(radius);
buffer.writeBoolean(spawnParticles);
}
@Override
public void readSpawnData(ByteBuf buffer) {
super.readSpawnData(buffer);
type = (MagicType.values()[buffer.readInt() % MagicType.values().length]);
radius = buffer.readFloat();
spawnParticles = buffer.readBoolean();
}
}