/**
Copyright (C) <2017> <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.mobs;
import net.minecraft.block.material.Material;
import net.minecraft.enchantment.EnchantmentHelper;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityCreature;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.SharedMonsterAttributes;
import net.minecraft.entity.monster.IMob;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.Items;
import net.minecraft.item.Item;
import net.minecraft.util.BlockPos;
import net.minecraft.util.DamageSource;
import net.minecraft.util.MathHelper;
import net.minecraft.util.Vec3;
import net.minecraft.world.EnumDifficulty;
import net.minecraft.world.EnumSkyBlock;
import net.minecraft.world.World;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
import zeldaswordskills.api.entity.ai.IEntityAnimationOffset;
import zeldaswordskills.entity.ZSSEntityInfo;
import zeldaswordskills.entity.buff.Buff;
import zeldaswordskills.entity.player.ZSSPlayerSkills;
import zeldaswordskills.entity.projectile.EntityBoomerang;
import zeldaswordskills.entity.projectile.EntitySwordBeam;
import zeldaswordskills.ref.Sounds;
import zeldaswordskills.skills.SkillBase;
import zeldaswordskills.util.PlayerUtils;
public abstract class EntityDekuBase extends EntityCreature implements IMob, IEntityAnimationOffset
{
/** Incrementing byte flag for #handleHealthUpdate to prevent action flag conflicts */
protected static byte flag_index = 10;
/** Byte flag for #handleHealthUpdate indicating that custom death animation should play */
public static final byte CUSTOM_DEATH = EntityDekuBase.flag_index++;
/**
* Set to other than 0 when a custom death animation should play. Uses an int
* to allow for different death animations. Only used client-side for rendering.
*/
public int custom_death;
/** Used client side to offset animations relying on ticksExisted for their timing */
private final int ticksExistedOffset;
public EntityDekuBase(World world) {
super(world);
this.addAITasks();
this.setSize(0.75F, 2.0F);
this.experienceValue = 5;
this.ticksExistedOffset = this.getTickOffset(world);
}
/**
* Return the {@link #ticksExistedOffset tick offset} for this entity's animations
*/
protected int getTickOffset(World world) {
return world.rand.nextInt(3600);
}
@Override
public int getTicksExistedOffset(int action_id) {
return ticksExistedOffset;
}
/**
* Called during entity construction to add AI tasks
*/
protected void addAITasks() {}
@Override
protected void applyEntityAttributes() {
super.applyEntityAttributes();
this.getEntityAttribute(SharedMonsterAttributes.movementSpeed).setBaseValue(0.0D);
this.getEntityAttribute(SharedMonsterAttributes.knockbackResistance).setBaseValue(1.0D);
this.getAttributeMap().registerAttribute(SharedMonsterAttributes.attackDamage).setBaseValue(3.0D);
ZSSEntityInfo.get(this).applyBuff(Buff.RESIST_STUN, Integer.MAX_VALUE, 100);
}
@Override
protected String getHurtSound() {
return Sounds.LEAF_HIT;
}
@Override
protected String getDeathSound() {
return Sounds.LEAF_HIT;
}
/**
* Prevents Deku Baba from being moved by most means
*/
@Override
public void addVelocity(double dx, double dy, double dz) {}
@Override
public boolean canBePushed() {
return false; // can't be pushed by entities
}
@Override
public boolean handleWaterMovement() {
return false; // can't be pushed by water
}
@Override
public boolean isInLava() {
return false; // can't be pushed by lava
}
/**
* Returns what {@link #isInLava()} should return so deku can be set on fire from lava
*/
public boolean isReallyInLava() {
return this.worldObj.isMaterialInBB(this.getEntityBoundingBox().expand(-0.10000000149011612D, -0.4000000059604645D, -0.10000000149011612D), Material.lava);
}
@Override
public void setSprinting(boolean sprinting) {}
@Override
protected void jump() {}
@Override
public void fall(float distance, float damageMultiplier) {}
@Override
protected boolean canDropLoot() {
return true;
}
/**
* Whether this entity is currently capable of attacking
*/
protected boolean canAttack() {
return true;
}
@Override
public boolean canAttackClass(Class<? extends EntityLivingBase> clazz) {
return !EntityDekuBase.class.isAssignableFrom(clazz) && super.canAttackClass(clazz);
}
@Override
public boolean canEntityBeSeen(Entity entity) {
Vec3 start = new Vec3(this.posX, this.posY + this.getEyeHeight(), this.posZ);
Vec3 end = new Vec3(entity.posX, entity.posY + entity.getEyeHeight(), entity.posZ);
return this.worldObj.rayTraceBlocks(start, end, false, true, false) == null;
}
@Override
public boolean attackEntityAsMob(Entity entity) {
if (!canAttack()) {
return false;
}
// copied from EntityMob
float f = (float)this.getEntityAttribute(SharedMonsterAttributes.attackDamage).getAttributeValue();
int i = 0;
if (entity instanceof EntityLivingBase) {
f += EnchantmentHelper.getModifierForCreature(this.getHeldItem(), ((EntityLivingBase) entity).getCreatureAttribute());
i += EnchantmentHelper.getKnockbackModifier(this);
}
boolean flag = entity.attackEntityFrom(DamageSource.causeMobDamage(this), f);
if (flag) {
if (i > 0) {
entity.addVelocity((double)(-MathHelper.sin(this.rotationYaw * (float)Math.PI / 180.0F) * (float)i * 0.5F), 0.1D, (double)(MathHelper.cos(this.rotationYaw * (float)Math.PI / 180.0F) * (float)i * 0.5F));
}
int j = EnchantmentHelper.getFireAspectModifier(this);
if (j > 0) {
entity.setFire(j * 4);
}
if (entity instanceof EntityLivingBase) {
EnchantmentHelper.applyThornEnchantments((EntityLivingBase) entity, this);
}
EnchantmentHelper.applyArthropodEnchantments(this, entity);
}
return flag;
}
/**
* Returns if the damage source is fatal to this deku in its current state
*/
protected boolean isSourceFatal(DamageSource source) {
if (source.getSourceOfDamage() instanceof EntityBoomerang || source.getSourceOfDamage() instanceof EntitySwordBeam) {
return true;
} else if (source.getEntity() instanceof EntityPlayer) {
return (ZSSPlayerSkills.get((EntityPlayer) source.getEntity()).isSkillActive(SkillBase.spinAttack));
} else if (source.isExplosion() && ZSSEntityInfo.get(this).hasIngestedBomb()) {
return true;
}
return false;
}
/**
* Returns true if the damage is a slashing type
*/
protected boolean isSlashing(DamageSource source) {
if (source.getSourceOfDamage() instanceof EntityLivingBase) {
EntityLivingBase entity = (EntityLivingBase) source.getSourceOfDamage();
if (PlayerUtils.isSword(entity.getHeldItem())) {
return true; // this covers all swords plus spin attack
}
} else if (source.isProjectile()) {
// TODO return source.getEntity() instanceof ISlashing
return source.getSourceOfDamage() instanceof EntityBoomerang || source.getSourceOfDamage() instanceof EntitySwordBeam;
}
return false;
}
@Override
public void onUpdate() {
super.onUpdate();
// Copied from Entity#onUpdate to allow being set on fire from lava due to #isInLava always returning false
if (this.isReallyInLava()) {
this.setOnFireFromLava();
}
if (!this.worldObj.isRemote && this.worldObj.getDifficulty() == EnumDifficulty.PEACEFUL) {
this.setDead();
}
}
@Override
public void onDeath(DamageSource source) {
super.onDeath(source);
if (!worldObj.isRemote) {
byte flag = getCustomDeathFlag(source);
if (flag != 0) {
worldObj.setEntityState(this, flag);
}
}
}
/**
* Return non-zero flag for a custom death animation; this flag will be sent to {@link #handleHealthUpdate(byte)}
*/
protected byte getCustomDeathFlag(DamageSource source) {
return (isSlashing(source) ? CUSTOM_DEATH : 0);
}
@Override
@SideOnly(Side.CLIENT)
public void handleStatusUpdate(byte flag) {
if (flag == CUSTOM_DEATH) {
custom_death = (this.rand.nextInt(2) == 0 ? -1 : 1);
} else {
super.handleStatusUpdate(flag);
}
}
@Override
protected Item getDropItem() {
return Items.stick;
}
@Override
public boolean getCanSpawnHere() {
if (this.worldObj.getDifficulty() == EnumDifficulty.PEACEFUL) {
return false;
} else if (!this.isValidSpawnBlock() || !this.isValidLightLevel()) {
return false;
}
return super.getCanSpawnHere();
}
/**
* Checks whether the block beneath the Deku is a valid block, e.g. grass or dirt,
* by calling {@link #isValidMaterial(Material)}
*/
protected boolean isValidSpawnBlock() {
return isValidMaterial(worldObj.getBlockState(this.getPosition().down()).getBlock().getMaterial());
}
/**
* Checks whether the block Material type is valid for purposes of spawning
*/
protected boolean isValidMaterial(Material material) {
return material == Material.grass || material == Material.ground ||
material == Material.clay || material == Material.sand;
}
/**
* Checks to make sure the light is not too bright where the mob is spawning
*/
protected boolean isValidLightLevel() {
BlockPos pos = new BlockPos(this.posX, this.getEntityBoundingBox().minY, this.posZ);
if (this.worldObj.getLightFor(EnumSkyBlock.SKY, pos) > this.rand.nextInt(32)) {
return false;
}
int light = this.worldObj.getLightFromNeighbors(pos);
if (this.worldObj.isThundering()) {
int sky = this.worldObj.getSkylightSubtracted();
this.worldObj.setSkylightSubtracted(10);
light = this.worldObj.getLightFromNeighbors(pos);
this.worldObj.setSkylightSubtracted(sky);
}
return light <= this.rand.nextInt(8);
}
}