/** Copyright (C) 2014 by jabelar This file is part of jabelar's Minecraft Forge modding examples; 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. For a copy of the GNU General Public License see <http://www.gnu.org/licenses/>. */ package com.blogspot.jabelarminecraft.wildanimals.entities.bigcats; import java.util.UUID; import net.minecraft.block.BlockColored; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityAgeable; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.SharedMonsterAttributes; import net.minecraft.entity.ai.EntityAIAttackOnCollide; import net.minecraft.entity.ai.EntityAIBase; import net.minecraft.entity.ai.EntityAIHurtByTarget; import net.minecraft.entity.ai.EntityAILeapAtTarget; import net.minecraft.entity.ai.EntityAILookIdle; import net.minecraft.entity.ai.EntityAIMate; import net.minecraft.entity.ai.EntityAIOwnerHurtByTarget; import net.minecraft.entity.ai.EntityAIOwnerHurtTarget; import net.minecraft.entity.ai.EntityAISwimming; import net.minecraft.entity.ai.EntityAITargetNonTamed; import net.minecraft.entity.ai.EntityAIWander; import net.minecraft.entity.ai.EntityAIWatchClosest; import net.minecraft.entity.monster.EntityCreeper; import net.minecraft.entity.monster.EntityGhast; import net.minecraft.entity.passive.EntityAnimal; import net.minecraft.entity.passive.EntityChicken; import net.minecraft.entity.passive.EntityCow; import net.minecraft.entity.passive.EntityHorse; import net.minecraft.entity.passive.EntityPig; import net.minecraft.entity.passive.EntitySheep; import net.minecraft.entity.passive.EntityTameable; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.init.Items; import net.minecraft.item.Item; import net.minecraft.item.ItemFood; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.pathfinding.PathEntity; import net.minecraft.util.DamageSource; import net.minecraft.util.MathHelper; import net.minecraft.world.World; import com.blogspot.jabelarminecraft.wildanimals.entities.IModEntity; import com.blogspot.jabelarminecraft.wildanimals.entities.ai.bigcat.EntityAIBegBigCat; import com.blogspot.jabelarminecraft.wildanimals.entities.ai.bigcat.EntityAIFollowBigCat; import com.blogspot.jabelarminecraft.wildanimals.entities.herdanimals.EntityHerdAnimal; import com.blogspot.jabelarminecraft.wildanimals.utilities.Utilities; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; public class EntityBigCat extends EntityTameable implements IModEntity { protected NBTTagCompound syncDataCompound = new NBTTagCompound(); /** * fields for controlling head tilt, like when interested or shaking */ protected float targetHeadAngle; protected float prevHeadAngle; /** * true if the bigCat is wet else false */ protected boolean isShaking; protected boolean startedShaking; protected final float TAMED_HEALTH = 20.0F; /** * This time increases while bigCat is shaking and emitting water particles. */ protected float timeBigCatIsShaking; protected float prevTimeBigCatIsShaking; // good to have instances of AI so task list can be modified, including in sub-classes protected EntityAIBase aiSwimming = new EntityAISwimming(this); protected EntityAIBase aiLeapAtTarget = new EntityAILeapAtTarget(this, 0.4F); protected EntityAIBase aiAttackOnCollide = new EntityAIAttackOnCollide(this, 1.0D, true); protected EntityAIBase aiFollowOwner = new EntityAIFollowBigCat(this, 1.0D, 10.0F, 2.0F); protected EntityAIBase aiMate = new EntityAIMate(this, 1.0D); protected EntityAIBase aiWander = new EntityAIWander(this, 1.0D); protected EntityAIBase aiBeg = new EntityAIBegBigCat(this, 8.0F); // in vanilla begging is only for wolf protected EntityAIBase aiWatchClosest = new EntityAIWatchClosest(this, EntityPlayer.class, 8.0F); protected EntityAIBase aiLookIdle = new EntityAILookIdle(this); protected EntityAIBase aiOwnerHurtByTarget = new EntityAIOwnerHurtByTarget(this); protected EntityAIBase aiOwnerHurtTarget = new EntityAIOwnerHurtTarget(this); protected EntityAIBase aiHurtByTarget = new EntityAIHurtByTarget(this, true); protected EntityAIBase aiTargetNonTamedSheep = new EntityAITargetNonTamed(this, EntitySheep.class, 200, false); protected EntityAIBase aiTargetNonTamedCow = new EntityAITargetNonTamed(this, EntityCow.class, 200, false); protected EntityAIBase aiTargetNonTamedPig = new EntityAITargetNonTamed(this, EntityPig.class, 200, false); protected EntityAIBase aiTargetNonTamedChicken = new EntityAITargetNonTamed(this, EntityChicken.class, 200, false); protected EntityAIBase aiTargetNonTamedHerdAnimal = new EntityAITargetNonTamed(this, EntityHerdAnimal.class, 200, false); public EntityBigCat(World parWorld) { super(parWorld); // // DEBUG // System.out.println("EntityBigCat constructor(), "+"on Client="+par1World.isRemote); setSize(1.0F, 1.33F); initSyncDataCompound(); setupAI(); } @Override public void setupAI() { getNavigator().setAvoidsWater(true); clearAITasks(); // clear any tasks assigned in super classes tasks.addTask(1, aiSwimming); tasks.addTask(2, aiSit); tasks.addTask(3, aiLeapAtTarget); tasks.addTask(4, aiAttackOnCollide); tasks.addTask(5, aiFollowOwner); tasks.addTask(6, aiMate); tasks.addTask(7, aiWander); tasks.addTask(8, aiBeg); // in vanilla begging is only for wolf tasks.addTask(9, aiWatchClosest); tasks.addTask(10, aiLookIdle); targetTasks.addTask(1, aiOwnerHurtByTarget); targetTasks.addTask(2, aiOwnerHurtTarget); targetTasks.addTask(3, aiHurtByTarget); targetTasks.addTask(4, aiTargetNonTamedSheep); targetTasks.addTask(4, aiTargetNonTamedCow); targetTasks.addTask(4, aiTargetNonTamedPig); targetTasks.addTask(4, aiTargetNonTamedChicken); targetTasks.addTask(4, aiTargetNonTamedHerdAnimal); } @Override public void initSyncDataCompound() { // // DEBUG // System.out.println("Initializing sync data compound"); syncDataCompound.setFloat("scaleFactor", 1.2F); syncDataCompound.setBoolean("isInterested", false); syncDataCompound.setBoolean("isTamed", false); syncDataCompound.setBoolean("isAngry", false); syncDataCompound.setBoolean("isSitting", false); syncDataCompound.setByte("collarColor", (byte) 0); syncDataCompound.setString("ownerName", ""); syncDataCompound.setLong("ownerUUIDMSB", 0); syncDataCompound.setLong("ownerUUIDLSB", 0); } // use clear tasks for subclasses then build up their ai task list specifically @Override public void clearAITasks() { tasks.taskEntries.clear(); targetTasks.taskEntries.clear(); } // you don't have to call this as it is called automatically during entityLiving subclass creation @Override protected void applyEntityAttributes() { super.applyEntityAttributes(); // registers the common attributes getEntityAttribute(SharedMonsterAttributes.maxHealth).setBaseValue(8.0D); getEntityAttribute(SharedMonsterAttributes.movementSpeed).setBaseValue(0.30D); getEntityAttribute(SharedMonsterAttributes.knockbackResistance).setBaseValue(0.0D); getEntityAttribute(SharedMonsterAttributes.followRange).setBaseValue(16.0D); // need to register any additional attributes getAttributeMap().registerAttribute(SharedMonsterAttributes.attackDamage); getEntityAttribute(SharedMonsterAttributes.attackDamage).setBaseValue(4.0D); } public void adjustEntityAttributes() { if (isTamed()) { getEntityAttribute(SharedMonsterAttributes.maxHealth).setBaseValue(TAMED_HEALTH); } } /** * Returns true if the newer Entity AI code should be run */ @Override public boolean isAIEnabled() { return true; } /** * Sets the active target the Task system uses for tracking */ @Override public void setAttackTarget(EntityLivingBase parEntityLivingBase) { super.setAttackTarget(parEntityLivingBase); if (parEntityLivingBase == null) { setAngry(false); } else if (!isTamed()) { setAngry(true); } } /** * main AI tick function, replaces updateEntityActionState */ @Override protected void updateAITick() { // Need to adjust attributes after the save data regarding // whether it is tamed is loaded and synced. if (ticksExisted == 10) { // note that the setTamed also forces a full NBT sync to client if (syncDataCompound.getBoolean("isTamed")) { setTamed(true); } else { setTamed(false); } adjustEntityAttributes(); } } @Override protected void entityInit() { super.entityInit(); } // @Override // protected void func_145780_a(int p_145780_1_, int p_145780_2_, int p_145780_3_, Block p_145780_4_) // { // playSound("wildanimals:mob.bigCat.step", 0.15F, 1.0F); // this is randomized from 1 to 5 // } @Override public void writeToNBT(NBTTagCompound parCompound) { // // DEBUG // System.out.println("Writing NBT"); super.writeToNBT(parCompound); parCompound.setTag("extendedPropsJabelar", syncDataCompound); } @Override public void readFromNBT(NBTTagCompound parCompound) { // // DEBUG // System.out.println("Reading NBT"); super.readFromNBT(parCompound); syncDataCompound = (NBTTagCompound) parCompound.getTag("extendedPropsJabelar"); sendEntitySyncPacket(); } /** * Returns the sound this mob makes while it's alive. */ @Override protected String getLivingSound() { return isAngry() ? "wildanimals:mob.bigCat.growl" : (rand.nextInt(3) == 0 ? (isTamed() && getHealth() < 10.0F ? "wildanimals:mob.bigCat.whine" : "wildanimals:mob.bigCat.panting") : "wildanimals:mob.bigCat.bark"); } /** * Returns the sound this mob makes when it is hurt. */ @Override protected String getHurtSound() { return "wildanimals:mob.bigCat.hurt"; // It uses sounds.json file to randomize and adds 1, 2 or 3 and .ogg } /** * Returns the sound this mob makes on death. */ @Override protected String getDeathSound() { return "wildanimals:mob.bigCat.death"; } /** * Returns the volume for the sounds this mob makes. */ @Override protected float getSoundVolume() { return 0.4F; } @Override protected Item getDropItem() { return Item.getItemById(-1); } /** * Called frequently so the entity can update its state every tick as required. For example, zombies and skeletons * use this to react to sunlight and start to burn. */ @Override public void onLivingUpdate() { super.onLivingUpdate(); if (!worldObj.isRemote && isShaking && !startedShaking && !hasPath() && onGround) { startedShaking = true; timeBigCatIsShaking = 0.0F; prevTimeBigCatIsShaking = 0.0F; worldObj.setEntityState(this, (byte)8); } } /** * Called to update the entity's position/logic. */ @Override public void onUpdate() { super.onUpdate(); prevHeadAngle = targetHeadAngle; if (getInterested()) { targetHeadAngle += (1.0F - targetHeadAngle) * 0.4F; numTicksToChaseTarget = 10; } else { targetHeadAngle += (0.0F - targetHeadAngle) * 0.4F; } if (isWet()) { isShaking = true; startedShaking = false; timeBigCatIsShaking = 0.0F; prevTimeBigCatIsShaking = 0.0F; } else if ((isShaking || startedShaking) && startedShaking) { if (timeBigCatIsShaking == 0.0F) { playSound("wildanimals:mob.bigCat.shake", getSoundVolume(), (rand.nextFloat() - rand.nextFloat()) * 0.2F + 1.0F); } prevTimeBigCatIsShaking = timeBigCatIsShaking; timeBigCatIsShaking += 0.05F; if (prevTimeBigCatIsShaking >= 2.0F) { isShaking = false; startedShaking = false; prevTimeBigCatIsShaking = 0.0F; timeBigCatIsShaking = 0.0F; } if (timeBigCatIsShaking > 0.4F) { float f = (float)boundingBox.minY; int i = (int)(MathHelper.sin((timeBigCatIsShaking - 0.4F) * (float)Math.PI) * 7.0F); for (int j = 0; j < i; ++j) { float f1 = (rand.nextFloat() * 2.0F - 1.0F) * width * 0.5F; float f2 = (rand.nextFloat() * 2.0F - 1.0F) * width * 0.5F; worldObj.spawnParticle("splash", posX + f1, f + 0.8F, posZ + f2, motionX, motionY, motionZ); } } } } @SideOnly(Side.CLIENT) public boolean getBigCatShaking() { return isShaking; } /** * Used when calculating the amount of shading to apply while the bigCat is shaking. */ @SideOnly(Side.CLIENT) public float getShadingWhileShaking(float parShakeRate) { return 0.75F + (prevTimeBigCatIsShaking + (timeBigCatIsShaking - prevTimeBigCatIsShaking) * parShakeRate) / 2.0F * 0.25F; } @SideOnly(Side.CLIENT) public float getShakeAngle(float parShakeRate, float par2) { float f2 = (prevTimeBigCatIsShaking + (timeBigCatIsShaking - prevTimeBigCatIsShaking) * parShakeRate + par2) / 1.8F; if (f2 < 0.0F) { f2 = 0.0F; } else if (f2 > 1.0F) { f2 = 1.0F; } return MathHelper.sin(f2 * (float)Math.PI) * MathHelper.sin(f2 * (float)Math.PI * 11.0F) * 0.15F * (float)Math.PI; } @Override public float getEyeHeight() { return height * 0.8F; } @SideOnly(Side.CLIENT) public float getInterestedAngle(float parRateOfAngleChange) { return (prevHeadAngle + (targetHeadAngle - prevHeadAngle) * parRateOfAngleChange) * 0.15F * (float)Math.PI; } /** * The speed it takes to move the entityliving's rotationPitch through the faceEntity method. This is only currently * use in wolves. */ @Override public int getVerticalFaceSpeed() { return isSitting() ? 20 : super.getVerticalFaceSpeed(); } /** * Called when the entity is attacked. */ @Override public boolean attackEntityFrom(DamageSource par1DamageSource, float par2) { if (isEntityInvulnerable()) { return false; } else { aiSit.setSitting(false); return super.attackEntityFrom(par1DamageSource, par2); } } /* * (non-Javadoc) * @see net.minecraft.entity.EntityLivingBase#attackEntityAsMob(net.minecraft.entity.Entity) */ @Override public boolean attackEntityAsMob(Entity par1Entity) { return par1Entity.attackEntityFrom(DamageSource.causeMobDamage(this), (float) getEntityAttribute(SharedMonsterAttributes.attackDamage).getAttributeValue()); } /** * Called when a player interacts with a mob. e.g. gets milk from a cow, gets into the saddle on a pig. */ @Override public boolean interact(EntityPlayer parPlayer) { // DEBUG System.out.println("EntityBigCat interact()"); ItemStack itemInHand = parPlayer.inventory.getCurrentItem(); // heal tamed with food if (isTamed()) { if (itemInHand != null) { if (itemInHand.getItem() instanceof ItemFood) { ItemFood itemfood = (ItemFood)itemInHand.getItem(); if (itemfood.isWolfsFavoriteMeat() && getHealth() < TAMED_HEALTH) { if (!parPlayer.capabilities.isCreativeMode) { --itemInHand.stackSize; } heal(itemfood.getHealAmount(itemInHand)); if (itemInHand.stackSize <= 0) { parPlayer.inventory.setInventorySlotContents(parPlayer.inventory.currentItem, (ItemStack)null); } return true; } } else if (itemInHand.getItem() == Items.dye) { int i = BlockColored.func_150032_b(itemInHand.getMetadata()); if (i != getCollarColor()) { setCollarColor(i); if (!parPlayer.capabilities.isCreativeMode && --itemInHand.stackSize <= 0) { parPlayer.inventory.setInventorySlotContents(parPlayer.inventory.currentItem, (ItemStack)null); } return true; } } } // toggle sitting if (parPlayer.getCommandSenderName().equalsIgnoreCase(getOwnerName()) && !worldObj.isRemote && !isBreedingItem(itemInHand)) { setSitting(!isSitting()); aiSit.setSitting(isSitting()); isJumping = false; setPathToEntity((PathEntity)null); setTarget((Entity)null); setAttackTarget((EntityLivingBase)null); } } // tame with bone else if (itemInHand != null && itemInHand.getItem() == Items.bone && !isAngry()) { if (!parPlayer.capabilities.isCreativeMode) { --itemInHand.stackSize; } if (itemInHand.stackSize <= 0) { parPlayer.inventory.setInventorySlotContents(parPlayer.inventory.currentItem, (ItemStack)null); } // Try taming if (!worldObj.isRemote) { if (rand.nextInt(3) == 0) { setTamed(true); setPathToEntity((PathEntity)null); setAttackTarget((EntityLivingBase)null); aiSit.setSitting(true); setHealth(TAMED_HEALTH); setOwner(parPlayer.getUniqueID()); playTameEffect(true); worldObj.setEntityState(this, (byte)7); // DEBUG System.out.println("Taming successful for owner = "+parPlayer.getCommandSenderName()); } else { playTameEffect(false); worldObj.setEntityState(this, (byte)6); } } } // grow with meat else if (itemInHand != null && itemInHand.getItem() == Items.beef && !isAngry()) { if (!parPlayer.capabilities.isCreativeMode) { --itemInHand.stackSize; } if (itemInHand.stackSize <= 0) { parPlayer.inventory.setInventorySlotContents(parPlayer.inventory.currentItem, (ItemStack)null); } if (!worldObj.isRemote) { if (rand.nextInt(3) == 0) { setGrowingAge(getAge()+500); worldObj.setEntityState(this, (byte)7); } else { playTameEffect(false); worldObj.setEntityState(this, (byte)6); } } return true; } return super.interact(parPlayer); } @Override public boolean isTamed() { boolean isTamed = syncDataCompound.getBoolean("isTamed"); return isTamed; } @Override public void setTamed(boolean parTamed) { syncDataCompound.setBoolean("isTamed", parTamed); // don't forget to sync client and server sendEntitySyncPacket(); if (parTamed) { getEntityAttribute(SharedMonsterAttributes.maxHealth).setBaseValue(TAMED_HEALTH); } else { getEntityAttribute(SharedMonsterAttributes.maxHealth).setBaseValue(8.0D); } } @Override public boolean isSitting() { return syncDataCompound.getBoolean("isSitting"); } @Override public void setSitting(boolean parIsSitting) { syncDataCompound.setBoolean("isSitting", parIsSitting); // don't forget to sync client and server sendEntitySyncPacket(); } public String getOwnerName() { return getOwner().getCommandSenderName(); } public void setOwner(UUID parOwnerUUID) { syncDataCompound.setLong("ownerUUIDMSB", parOwnerUUID.getMostSignificantBits()); syncDataCompound.setLong("ownerUUIDLSB", parOwnerUUID.getLeastSignificantBits()); // don't forget to sync client and server sendEntitySyncPacket(); } @Override public EntityLivingBase getOwner() { UUID uuid = new UUID(syncDataCompound.getLong("ownerUUIDMSB"), syncDataCompound.getLong("ownerUUIDLSB")); return worldObj.getPlayerEntityByUUID(uuid); } @Override @SideOnly(Side.CLIENT) public void handleHealthUpdate(byte par1) { if (par1 == 8) { startedShaking = true; timeBigCatIsShaking = 0.0F; prevTimeBigCatIsShaking = 0.0F; } else { super.handleHealthUpdate(par1); } } @SideOnly(Side.CLIENT) public float getTailRotation() { return isAngry() ? 1.5393804F : (isTamed() ? (0.55F - (TAMED_HEALTH - getHealth()) * 0.02F) * (float)Math.PI : ((float)Math.PI / 5F)); } /** * Checks if the parameter is an item which this animal can be fed to breed it (wheat, carrots or seeds depending on * the animal type) */ @Override public boolean isBreedingItem(ItemStack par1ItemStack) { return par1ItemStack == null ? false : (!(par1ItemStack.getItem() instanceof ItemFood) ? false : ((ItemFood)par1ItemStack.getItem()).isWolfsFavoriteMeat()); } /** * Will return how many at most can spawn in a chunk at once. */ @Override public int getMaxSpawnedInChunk() { return 8; } /** * Determines whether this bigCat is angry or not. */ public boolean isAngry() { return syncDataCompound.getBoolean("isAngry"); } /** * Sets whether this bigCat is angry or not. */ public void setAngry(boolean parIsAngry) { syncDataCompound.setBoolean("isAngry", parIsAngry); // don't forget to sync client and server sendEntitySyncPacket(); } /** * Return this bigCat's collar color. */ public int getCollarColor() { return syncDataCompound.getByte("collarColor"); } /** * Set this bigCat's collar color. */ public void setCollarColor(int parCollarColor) { syncDataCompound.setByte("collarColor", (byte) parCollarColor); // don't forget to sync client and server sendEntitySyncPacket(); } @Override public EntityBigCat createChild(EntityAgeable par1EntityAgeable) { // DEBUG System.out.println("EntityBigCat createChild()"); EntityBigCat entitybigCat = new EntityBigCat(worldObj); String s = func_152113_b(); // used to be getOwnerName(); if (s != null && s.trim().length() > 0) { entitybigCat.func_152115_b(s); // used to be setOwner(s); entitybigCat.setTamed(true); } return entitybigCat; } public void setInterested(boolean parIsInterested) { syncDataCompound.setBoolean("isInterested", parIsInterested); // don't forget to sync client and server sendEntitySyncPacket(); } public boolean getInterested() { return syncDataCompound.getBoolean("isInterested"); } /** * Returns true if the mob is currently able to mate with the specified mob. */ @Override public boolean canMateWith(EntityAnimal parEntityAnimal) { if (parEntityAnimal == this) { return false; } else if (!isTamed()) { return false; } else if (!(parEntityAnimal instanceof EntityBigCat)) { return false; } else { EntityBigCat entitybigCat = (EntityBigCat)parEntityAnimal; // DEBUG System.out.println("Found mate = "+entitybigCat); return !entitybigCat.isTamed() ? false : (entitybigCat.isSitting() ? false : isInLove() && entitybigCat.isInLove()); } } /** * Determines if an entity can be despawned, used on idle far away entities */ @Override protected boolean canDespawn() { return !isTamed() && ticksExisted > 2400; } /* * Used by the EntityAIOwnerHurt target and EntityAIOwnerHurtByTarget classes to identity * suitable attack targets */ @Override public boolean func_142018_a(EntityLivingBase par1EntityLivingBase, EntityLivingBase par2EntityLivingBase) { if (!(par1EntityLivingBase instanceof EntityCreeper) && !(par1EntityLivingBase instanceof EntityGhast)) { if (par1EntityLivingBase instanceof EntityBigCat) { EntityBigCat entitybigCat = (EntityBigCat)par1EntityLivingBase; if (entitybigCat.isTamed() && entitybigCat.getOwner() == par2EntityLivingBase) { return false; } } return par1EntityLivingBase instanceof EntityPlayer && par2EntityLivingBase instanceof EntityPlayer && !((EntityPlayer)par2EntityLivingBase).canAttackPlayer((EntityPlayer)par1EntityLivingBase) ? false : !(par1EntityLivingBase instanceof EntityHorse) || !((EntityHorse)par1EntityLivingBase).isTame(); } else { return false; } } // ***************************************************** // ENCAPSULATION SETTER AND GETTER METHODS // Don't forget to send sync packets in setters // ***************************************************** @Override public void setScaleFactor(float parScaleFactor) { syncDataCompound.setFloat("scaleFactor", Math.abs(parScaleFactor)); // don't forget to sync client and server sendEntitySyncPacket(); } @Override public float getScaleFactor() { return syncDataCompound.getFloat("scaleFactor"); } @Override public void sendEntitySyncPacket() { Utilities.sendEntitySyncPacketToClient(this); } @Override public NBTTagCompound getSyncDataCompound() { return syncDataCompound; } @Override public void setSyncDataCompound(NBTTagCompound parCompound) { syncDataCompound = parCompound; } }