package electroblob.wizardry.entity.living; import electroblob.wizardry.Wizardry; import electroblob.wizardry.block.BlockReceptacle; import electroblob.wizardry.constants.Element; import electroblob.wizardry.registry.WizardrySounds; import electroblob.wizardry.util.ParticleBuilder; import net.minecraft.entity.EntityLiving; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.IEntityLivingData; import net.minecraft.entity.SharedMonsterAttributes; import net.minecraft.entity.ai.*; import net.minecraft.entity.monster.EntityMob; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.nbt.NBTUtil; import net.minecraft.network.datasync.DataParameter; import net.minecraft.network.datasync.DataSerializers; import net.minecraft.network.datasync.EntityDataManager; import net.minecraft.util.DamageSource; import net.minecraft.util.ResourceLocation; import net.minecraft.util.SoundEvent; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.Vec3d; import net.minecraft.world.DifficultyInstance; import net.minecraft.world.World; import javax.annotation.Nullable; public class EntityRemnant extends EntityMob { /** Data parameter for the remnant's element. */ private static final DataParameter ELEMENT = EntityDataManager.createKey(EntityRemnant.class, DataSerializers.VARINT); /** Data parameter that tracks whether the remnant is currently attacking (charging). */ private static final DataParameter ATTACKING = EntityDataManager.createKey(EntityRemnant.class, DataSerializers.BOOLEAN); private ResourceLocation lootTable; @Nullable private BlockPos boundOrigin; public EntityRemnant(World world){ super(world); this.setSize(0.8f, 0.8f); this.moveHelper = new EntityRemnant.AIMoveControl(this); this.experienceValue = 8; } @Override protected void entityInit(){ super.entityInit(); this.dataManager.register(ELEMENT, 1); // Default to fire this.dataManager.register(ATTACKING, false); // Default to fire } @Override protected void initEntityAI(){ super.initEntityAI(); this.tasks.addTask(0, new EntityAISwimming(this)); this.tasks.addTask(4, new EntityRemnant.AIChargeAttack()); this.tasks.addTask(8, new EntityRemnant.AIMoveRandom()); this.tasks.addTask(9, new EntityAIWatchClosest(this, EntityPlayer.class, 3.0F, 1.0F)); this.tasks.addTask(10, new EntityAIWatchClosest(this, EntityLiving.class, 8.0F)); this.targetTasks.addTask(1, new EntityAIHurtByTarget(this, true, EntityRemnant.class)); this.targetTasks.addTask(3, new EntityAINearestAttackableTarget<>(this, EntityPlayer.class, true)); } @Override protected void applyEntityAttributes(){ super.applyEntityAttributes(); this.getEntityAttribute(SharedMonsterAttributes.MAX_HEALTH).setBaseValue(16); this.getEntityAttribute(SharedMonsterAttributes.ATTACK_DAMAGE).setBaseValue(4); } @Nullable @Override public IEntityLivingData onInitialSpawn(DifficultyInstance difficulty, @Nullable IEntityLivingData livingdata){ this.setElement(Element.values()[1 + rand.nextInt(Element.values().length - 1)]); // Exclude MAGIC this.setBoundOrigin(new BlockPos(this)); return super.onInitialSpawn(difficulty, livingdata); } public Element getElement(){ return Element.values()[this.dataManager.get(ELEMENT)]; } public void setElement(Element element){ this.dataManager.set(ELEMENT, element.ordinal()); this.lootTable = new ResourceLocation(Wizardry.MODID, "entities/remnant/" + element.getName()); } public boolean isAttacking(){ return this.dataManager.get(ATTACKING); } public void setAttacking(boolean attacking){ this.dataManager.set(ATTACKING, attacking); } @Nullable public BlockPos getBoundOrigin(){ return this.boundOrigin; } public void setBoundOrigin(@Nullable BlockPos boundOriginIn){ this.boundOrigin = boundOriginIn; } @Override protected boolean isValidLightLevel(){ return true; } @Override public float getBlockPathWeight(BlockPos pos){ // Goddamnit Minecraft, stop imposing obscure spawning restrictions return 1; // This won't affect pathfinding since remnants are flying mobs anyway } @Override protected float applyPotionDamageCalculations(DamageSource source, float damage){ damage = super.applyPotionDamageCalculations(source, damage); if(source.isMagicDamage()) damage *= 0.25f; // Remnants are 75% resistant to magic damage return damage; } @Override public void onUpdate(){ // Use the same trick as EntityVex to fly through stuff this.noClip = true; super.onUpdate(); this.noClip = false; this.setNoGravity(true); if(world.isRemote){ Vec3d centre = this.getPositionVector().add(0, height/2, 0); int[] colours = BlockReceptacle.PARTICLE_COLOURS.get(this.getElement()); if(rand.nextInt(10) == 0){ ParticleBuilder.create(ParticleBuilder.Type.FLASH).entity(this).pos(0, height/2, 0).scale(width).time(48).clr(colours[0]).spawn(world); } double r = width/3; double x = r * (rand.nextDouble() * 2 - 1); double y = r * (rand.nextDouble() * 2 - 1); double z = r * (rand.nextDouble() * 2 - 1); if(this.deathTime > 0){ // Spew out particles on death for(int i = 0; i < 8; i++){ ParticleBuilder.create(ParticleBuilder.Type.DUST, rand, centre.x + x, centre.y + y, centre.z + z, 0.1, true) .time(12).clr(colours[1]).fade(colours[2]).spawn(world); } }else{ ParticleBuilder.create(ParticleBuilder.Type.DUST).pos(centre.x + x, centre.y + y, centre.z + z) .vel(x * -0.03, 0.02, z * -0.03).time(24 + rand.nextInt(8)).clr(colours[1]).fade(colours[2]).spawn(world); } } } @Override protected SoundEvent getAmbientSound(){ return WizardrySounds.ENTITY_REMNANT_AMBIENT; } @Override protected SoundEvent getDeathSound(){ return WizardrySounds.ENTITY_REMNANT_DEATH; } @Override protected SoundEvent getHurtSound(DamageSource source){ return WizardrySounds.ENTITY_REMNANT_HURT; } @Nullable @Override protected ResourceLocation getLootTable(){ return lootTable; } @Override public void readEntityFromNBT(NBTTagCompound nbt){ super.readEntityFromNBT(nbt); this.setElement(Element.values()[nbt.getInteger("Element")]); if(nbt.hasKey("BoundOrigin")) boundOrigin = NBTUtil.getPosFromTag(nbt.getCompoundTag("BoundOrigin")); } @Override public void writeEntityToNBT(NBTTagCompound nbt){ super.writeEntityToNBT(nbt); nbt.setInteger("Element", this.getElement().ordinal()); if(boundOrigin != null) nbt.setTag("BoundOrigin", NBTUtil.createPosTag(boundOrigin)); } // AI classes (copied from EntityVex) class AIChargeAttack extends EntityAIBase { public AIChargeAttack(){ this.setMutexBits(1); } @Override public boolean shouldExecute(){ if(EntityRemnant.this.getAttackTarget() != null && !EntityRemnant.this.getMoveHelper().isUpdating() && EntityRemnant.this.rand.nextInt(7) == 0){ return EntityRemnant.this.getDistanceSq(EntityRemnant.this.getAttackTarget()) > 4.0D; }else{ return false; } } @Override public boolean shouldContinueExecuting(){ return EntityRemnant.this.getMoveHelper().isUpdating() && EntityRemnant.this.isAttacking() && EntityRemnant.this.getAttackTarget() != null && EntityRemnant.this.getAttackTarget().isEntityAlive(); } @Override public void startExecuting(){ EntityLivingBase entitylivingbase = EntityRemnant.this.getAttackTarget(); if(entitylivingbase == null) return; Vec3d vec3d = entitylivingbase.getPositionEyes(1.0F); EntityRemnant.this.moveHelper.setMoveTo(vec3d.x, vec3d.y, vec3d.z, 1.0D); EntityRemnant.this.setAttacking(true); // EntityRemnant.this.playSound(SoundEvents.ENTITY_VEX_CHARGE, 1.0F, 1.0F); } @Override public void resetTask(){ EntityRemnant.this.setAttacking(false); } @Override public void updateTask(){ EntityLivingBase entitylivingbase = EntityRemnant.this.getAttackTarget(); if(entitylivingbase == null) return; if(EntityRemnant.this.getEntityBoundingBox().intersects(entitylivingbase.getEntityBoundingBox())){ EntityRemnant.this.attackEntityAsMob(entitylivingbase); EntityRemnant.this.setAttacking(false); }else{ double d0 = EntityRemnant.this.getDistanceSq(entitylivingbase); if(d0 < 9.0D){ Vec3d vec3d = entitylivingbase.getPositionEyes(1.0F); EntityRemnant.this.moveHelper.setMoveTo(vec3d.x, vec3d.y, vec3d.z, 1.0D); } } } } class AIMoveControl extends EntityMoveHelper { public AIMoveControl(EntityRemnant host){ super(host); } @Override public void onUpdateMoveHelper(){ if(this.action == EntityMoveHelper.Action.MOVE_TO){ double d0 = this.posX - EntityRemnant.this.posX; double d1 = this.posY - EntityRemnant.this.posY; double d2 = this.posZ - EntityRemnant.this.posZ; double d3 = d0 * d0 + d1 * d1 + d2 * d2; d3 = MathHelper.sqrt(d3); if(d3 < EntityRemnant.this.getEntityBoundingBox().getAverageEdgeLength()){ this.action = EntityMoveHelper.Action.WAIT; EntityRemnant.this.motionX *= 0.5D; EntityRemnant.this.motionY *= 0.5D; EntityRemnant.this.motionZ *= 0.5D; }else{ EntityRemnant.this.motionX += d0 / d3 * 0.05D * this.speed; EntityRemnant.this.motionY += d1 / d3 * 0.05D * this.speed; EntityRemnant.this.motionZ += d2 / d3 * 0.05D * this.speed; if(EntityRemnant.this.getAttackTarget() == null){ EntityRemnant.this.rotationYaw = -((float)MathHelper.atan2(EntityRemnant.this.motionX, EntityRemnant.this.motionZ)) * (180F / (float)Math.PI); }else{ double d4 = EntityRemnant.this.getAttackTarget().posX - EntityRemnant.this.posX; double d5 = EntityRemnant.this.getAttackTarget().posZ - EntityRemnant.this.posZ; EntityRemnant.this.rotationYaw = -((float)MathHelper.atan2(d4, d5)) * (180F / (float)Math.PI); } EntityRemnant.this.renderYawOffset = EntityRemnant.this.rotationYaw; } } } } class AIMoveRandom extends EntityAIBase { public AIMoveRandom(){ this.setMutexBits(1); } @Override public boolean shouldExecute(){ return !EntityRemnant.this.getMoveHelper().isUpdating() && EntityRemnant.this.rand.nextInt(7) == 0; } @Override public boolean shouldContinueExecuting(){ return false; } @Override public void updateTask(){ BlockPos blockpos = EntityRemnant.this.getBoundOrigin(); if(blockpos == null){ blockpos = new BlockPos(EntityRemnant.this); } for(int i = 0; i < 3; ++i){ BlockPos blockpos1 = blockpos.add(EntityRemnant.this.rand.nextInt(15) - 7, EntityRemnant.this.rand.nextInt(11) - 5, EntityRemnant.this.rand.nextInt(15) - 7); if(EntityRemnant.this.world.isAirBlock(blockpos1)){ EntityRemnant.this.moveHelper.setMoveTo((double)blockpos1.getX() + 0.5D, (double)blockpos1.getY() + 0.5D, (double)blockpos1.getZ() + 0.5D, 0.25D); if(EntityRemnant.this.getAttackTarget() == null){ EntityRemnant.this.getLookHelper().setLookPosition((double)blockpos1.getX() + 0.5D, (double)blockpos1.getY() + 0.5D, (double)blockpos1.getZ() + 0.5D, 180.0F, 20.0F); } break; } } } } }