/**
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 java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import net.minecraft.block.material.Material;
import net.minecraft.enchantment.EnchantmentHelper;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.EnumCreatureAttribute;
import net.minecraft.entity.SharedMonsterAttributes;
import net.minecraft.entity.ai.EntityAIBase;
import net.minecraft.entity.monster.IMob;
import net.minecraft.entity.passive.EntityWaterMob;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.Items;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.util.DamageSource;
import net.minecraft.util.MathHelper;
import net.minecraft.world.EnumDifficulty;
import net.minecraft.world.World;
import zeldaswordskills.api.block.IWhipBlock.WhipType;
import zeldaswordskills.api.entity.BombType;
import zeldaswordskills.api.entity.IEntityLootable;
import zeldaswordskills.entity.IEntityVariant;
import zeldaswordskills.entity.projectile.EntityBomb;
import zeldaswordskills.entity.projectile.EntityThrowingRock;
import zeldaswordskills.item.ItemTreasure.Treasures;
import zeldaswordskills.item.ZSSItems;
import zeldaswordskills.ref.Config;
import zeldaswordskills.util.BiomeType;
import zeldaswordskills.util.TargetUtils;
// TODO switch attack logic to use AI system
public class EntityOctorok extends EntityWaterMob implements IMob, IEntityLootable, IEntityVariant
{
/**
* Returns array of default biomes in which this entity may spawn naturally
*/
public static String[] getDefaultBiomes() {
List<String> biomes = new ArrayList<String>();
biomes.addAll(Arrays.asList(BiomeType.BEACH.defaultBiomes));
biomes.addAll(Arrays.asList(BiomeType.OCEAN.defaultBiomes));
return biomes.toArray(new String[biomes.size()]);
}
/** Squid type data watcher index (skeleton's use 13) */
private static final int OCTOROK_TYPE_INDEX = 13;
/** Squid-related fields for random movement and rendering */
public float squidPitch;
public float prevSquidPitch;
public float squidYaw;
public float prevSquidYaw;
public float squidRotation;
public float prevSquidRotation;
public float tentacleAngle;
public float prevTentacleAngle;
private float randomMotionSpeed;
private float rotationVelocity;
private float field_70871_bB;
private float randomMotionVecX;
private float randomMotionVecY;
private float randomMotionVecZ;
/** Replacement for removed 'attackTime' */
protected int attackTime;
public EntityOctorok(World world) {
super(world);
experienceValue = 5;
setSize(0.95F, 0.95F);
rand.setSeed((long)(1 + getEntityId()));
rotationVelocity = 1.0F / (rand.nextFloat() + 1.0F) * 0.2F;
tasks.addTask(0, new EntityOctorok.AIMoveRandom());
}
@Override
public void entityInit() {
super.entityInit();
dataWatcher.addObject(OCTOROK_TYPE_INDEX, (byte)(rand.nextInt(5) == 0 ? 1 : 0));
}
/**
* Returns the octorok's type: 0 - normal, 1 - bomb shooter
*/
public int getType() {
return dataWatcher.getWatchableObjectByte(OCTOROK_TYPE_INDEX);
}
/**
* Sets the octorok's type: 0 - normal, 1 - bomb shooter
*/
@Override
public EntityOctorok setType(int type) {
dataWatcher.updateObject(OCTOROK_TYPE_INDEX, (byte)(type % 2));
return this;
}
@Override
protected float getSoundVolume() {
return 0.4F;
}
@Override
protected boolean canTriggerWalking() {
return false;
}
@Override
public boolean canAttackClass(Class<? extends EntityLivingBase> clazz) {
return super.canAttackClass(clazz) && clazz != EntityOctorok.class;
}
@Override
public void onUpdate() {
super.onUpdate();
if (!worldObj.isRemote && worldObj.getDifficulty() == EnumDifficulty.PEACEFUL) {
setDead();
}
updateAttackTarget();
}
@Override
public void onLivingUpdate() {
super.onLivingUpdate();
prevSquidPitch = squidPitch;
prevSquidYaw = squidYaw;
prevSquidRotation = squidRotation;
prevTentacleAngle = tentacleAngle;
squidRotation += rotationVelocity;
if (squidRotation > ((float) Math.PI * 2F)) {
squidRotation -= ((float) Math.PI * 2F);
if (rand.nextInt(10) == 0) {
rotationVelocity = 1.0F / (rand.nextFloat() + 1.0F) * 0.2F;
}
}
if (isInWater()) {
float f;
if (squidRotation < (float) Math.PI) {
f = squidRotation / (float) Math.PI;
tentacleAngle = MathHelper.sin(f * f * (float) Math.PI) * (float) Math.PI * 0.25F;
if ((double) f > 0.75D) {
randomMotionSpeed = 1.0F;
field_70871_bB = 1.0F;
} else {
field_70871_bB *= 0.8F;
}
} else {
tentacleAngle = 0.0F;
randomMotionSpeed *= 0.9F;
field_70871_bB *= 0.99F;
}
if (!worldObj.isRemote) {
Entity target = getAttackTarget();
if (target != null) {
TargetUtils.setEntityHeading(this, randomMotionVecX, randomMotionVecY, randomMotionVecZ, 0.25F, 1.0F, false);
faceEntity(target, 30.0F, 120.0F); // squid model values: 30.0F, 210.0F
} else {
motionX = (double)(randomMotionVecX * randomMotionSpeed);
motionY = (double)(randomMotionVecY * randomMotionSpeed);
motionZ = (double)(randomMotionVecZ * randomMotionSpeed);
}
}
renderYawOffset += (-((float) Math.atan2(motionX, motionZ)) * 180.0F / (float) Math.PI - renderYawOffset) * 0.1F;
rotationYaw = renderYawOffset;
squidYaw += (float) Math.PI * field_70871_bB * 1.5F;
f = MathHelper.sqrt_double(motionX * motionX + motionZ * motionZ);
squidPitch += (-((float) Math.atan2((double) f, motionY)) * 180.0F / (float) Math.PI - squidPitch) * 0.1F;
} else {
tentacleAngle = MathHelper.abs(MathHelper.sin(squidRotation)) * (float) Math.PI * 0.25F;
squidPitch = (float)((double) squidPitch + (double)(-90.0F - squidPitch) * 0.02D);
if (!worldObj.isRemote) {
motionX = 0.0D;
motionY -= 0.08D;
motionY *= 0.9800000190734863D;
motionZ = 0.0D;
}
}
}
@Override
public void moveEntityWithHeading(float dx, float dz) {
moveEntity(motionX, motionY, motionZ);
}
/**
* Updates the current attack target
*/
protected void updateAttackTarget() {
float distance = 0.0F;
EntityLivingBase entityToAttack = getAttackTarget();
if (entityToAttack == null) {
entityToAttack = findPlayerToAttack();
} else if (entityToAttack.isEntityAlive() && canAttackClass(entityToAttack.getClass())) {
distance = entityToAttack.getDistanceToEntity(this);
if (distance > 16.0F) {
entityToAttack = null;
} else if (canEntityBeSeen(entityToAttack)) {
attackEntity(entityToAttack, distance);
}
} else {
entityToAttack = null;
}
if (entityToAttack != getAttackTarget()) {
setAttackTarget(entityToAttack);
}
}
@Override
protected void applyEntityAttributes() {
super.applyEntityAttributes();
getEntityAttribute(SharedMonsterAttributes.maxHealth).setBaseValue(12.0D);
getEntityAttribute(SharedMonsterAttributes.knockbackResistance).setBaseValue(0.75D);
getAttributeMap().registerAttribute(SharedMonsterAttributes.attackDamage).setBaseValue(2.0D);
}
@Override
public EnumCreatureAttribute getCreatureAttribute() {
return EnumCreatureAttribute.ARTHROPOD;
}
@Override
public boolean isInWater() {
return worldObj.handleMaterialAcceleration(getEntityBoundingBox().expand(0.0D, -0.6000000238418579D, 0.0D), Material.water, this);
}
@Override
public boolean getCanSpawnHere() {
return posY > 45.0D && posY < 63.0D && worldObj.getDifficulty() != EnumDifficulty.PEACEFUL && super.getCanSpawnHere();
}
@Override
public int getTotalArmorValue() {
return Math.min(super.getTotalArmorValue() + 2, 20);
}
// @Override
protected EntityLivingBase findPlayerToAttack() {
EntityPlayer entityplayer = worldObj.getClosestPlayerToEntity(this, 16.0D);
return entityplayer != null && canEntityBeSeen(entityplayer) ? entityplayer : null;
}
@Override
public boolean attackEntityFrom(DamageSource source, float amount) {
if (isEntityInvulnerable(source) || source.isExplosion()) {
return false;
} else if (super.attackEntityFrom(source, amount)) {
Entity entity = source.getEntity();
if (entity != this && entity instanceof EntityLivingBase && riddenByEntity != entity && ridingEntity != entity) {
setAttackTarget((EntityLivingBase) entity);
return true;
} else {
return true;
}
} else {
return false;
}
}
/**
* Attack entity with 'touch of death'
*/
@Override
public boolean attackEntityAsMob(Entity entity) {
attackTime = 20;
float damage = (float) getEntityAttribute(SharedMonsterAttributes.attackDamage).getAttributeValue();
int knockback = 0;
if (entity instanceof EntityLivingBase) {
damage += EnchantmentHelper.getModifierForCreature(getHeldItem(), ((EntityLivingBase) entity).getCreatureAttribute());
knockback += EnchantmentHelper.getKnockbackModifier(this);
}
boolean flag = entity.attackEntityFrom(DamageSource.causeMobDamage(this), damage);
if (flag) {
if (knockback > 0) {
entity.addVelocity((double)(-MathHelper.sin(rotationYaw * (float)Math.PI / 180.0F) * (float)knockback * 0.5F), 0.1D, (double)(MathHelper.cos(rotationYaw * (float)Math.PI / 180.0F) * (float)knockback * 0.5F));
motionX *= 0.6D;
motionZ *= 0.6D;
}
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;
}
/**
* Attack entity with ranged attack
*/
protected void attackEntityWithRangedAttack(EntityLivingBase entity) {
attackTime = rand.nextInt(20) + rand.nextInt(20) + 20;
float f = (float) getEntityAttribute(SharedMonsterAttributes.attackDamage).getAttributeValue();
Entity projectile;
int difficulty = worldObj.getDifficulty().getDifficultyId();
if (getType() == 1) {
projectile = new EntityBomb(worldObj, this, (EntityLivingBase) entity, 1.0F, (float)(14 - difficulty * 4)).
setType(BombType.BOMB_WATER).setFuseTime(12 - (difficulty * 2)).setNoGrief().setMotionFactor(0.25F).setDamage(f * 2.0F * difficulty);
} else {
projectile = new EntityThrowingRock(worldObj, this, (EntityLivingBase) entity, 1.0F, (float)(14 - difficulty * 4)).
setIgnoreWater().setDamage(f * difficulty);
}
// TODO worldObj.playSoundAtEntity(this, ModInfo.SOUND_WEB_SPLAT, 1.0F, 1.0F / (rand.nextFloat() * 0.4F + 1.0F));
if (!worldObj.isRemote) {
worldObj.spawnEntityInWorld(projectile);
}
}
/**
* Determines which type of attack (melee or ranged) to perform against the target entity
*/
protected void attackEntity(Entity entity, float distance) {
if (attackTime <= 0) {
if (distance < 2.0F && entity.getEntityBoundingBox().maxY > getEntityBoundingBox().minY && entity.getEntityBoundingBox().minY < getEntityBoundingBox().maxY) {
attackEntityAsMob(entity);
} else if (rand.nextInt(60) == 0 && entity instanceof EntityLivingBase) {
attackEntityWithRangedAttack((EntityLivingBase) entity);
}
}
}
@Override
protected Item getDropItem() {
return (getType() > 0 || rand.nextFloat() < 0.5F ? Items.dye : ZSSItems.throwingRock);
}
@Override
protected void dropFewItems(boolean recentlyHit, int lootingLevel) {
int j = rand.nextInt(2 + lootingLevel) + 1;
for (int k = 0; k < j; ++k) {
entityDropItem(new ItemStack(getDropItem(), 1, 0), 0.0F);
}
}
@Override
protected void addRandomDrop() {
switch(rand.nextInt(8)) {
case 0:
entityDropItem(new ItemStack(ZSSItems.treasure, 1, Treasures.TENTACLE.ordinal()), 0.0F);
break;
default:
if (getType() == 1) {
entityDropItem(new ItemStack(ZSSItems.bomb, 1, BombType.BOMB_WATER.ordinal()), 0.0F);
} else {
entityDropItem(new ItemStack(rand.nextInt(3) == 0 ? Items.emerald : ZSSItems.smallHeart), 0.0F);
}
}
}
@Override
public float getLootableChance(EntityPlayer player, WhipType whip) {
return 0.2F;
}
@Override
public ItemStack getEntityLoot(EntityPlayer player, WhipType whip) {
if (rand.nextFloat() < (0.1F * (1 + whip.ordinal()))) {
return new ItemStack(ZSSItems.treasure, 1, Treasures.TENTACLE.ordinal());
} else if (getType() == 1 && rand.nextFloat() < (0.1F * (1 + whip.ordinal()))) {
return new ItemStack(ZSSItems.bomb, 1, BombType.BOMB_WATER.ordinal());
}
return new ItemStack(getDropItem(), 1, 0);
}
@Override
public boolean onLootStolen(EntityPlayer player, boolean wasItemStolen) {
return true;
}
@Override
public boolean isHurtOnTheft(EntityPlayer player, WhipType whip) {
return Config.getHurtOnSteal();
}
@Override
public void writeEntityToNBT(NBTTagCompound compound) {
super.writeEntityToNBT(compound);
compound.setByte("octorokType", (byte) getType());
}
@Override
public void readEntityFromNBT(NBTTagCompound compound) {
super.readEntityFromNBT(compound);
if (compound.hasKey("octorokType")) {
setType(compound.getByte("octorokType"));
}
}
//========================= Below this line copied from EntitySquid ===================//
public void setRandomMotion(float randomMotionX, float randomMotionY, float randomMotionZ) {
this.randomMotionVecX = randomMotionX;
this.randomMotionVecY = randomMotionY;
this.randomMotionVecZ = randomMotionZ;
}
public boolean isMovingRandomly() {
return randomMotionVecX != 0.0F || randomMotionVecY != 0.0F || randomMotionVecZ != 0.0F;
}
class AIMoveRandom extends EntityAIBase {
private EntityOctorok entity = EntityOctorok.this;
@Override
public boolean shouldExecute() {
return true;
}
@Override
public void updateTask() {
int i = entity.getAge();
Entity target = entity.getAttackTarget();
if (i > 100) {
entity.setRandomMotion(0.0F, 0.0F, 0.0F);
} else if (target != null && entity.getRNG().nextInt(25) == 0) {
float dx = (float)(target.posX - posX) * 0.015F;
float dy = (float)(1 + target.posY - posY) * 0.015F;
float dz = (float)(target.posZ - posZ) * 0.015F;
entity.setRandomMotion(dx, dy, dz);
} else if (entity.getRNG().nextInt(50) == 0 || !entity.inWater || !entity.isMovingRandomly()) {
float f = entity.getRNG().nextFloat() * (float)Math.PI * 2.0F;
float dx = MathHelper.cos(f) * 0.2F;
float dy = -0.1F + entity.getRNG().nextFloat() * 0.2F;
float dz = MathHelper.sin(f) * 0.2F;
entity.setRandomMotion(dx, dy, dz);
}
}
}
}