/** 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(); } }