From ed1bc4e7f7e3bce76bc191269879396c1b200787 Mon Sep 17 00:00:00 2001 From: Me <135455255+IcarussOne@users.noreply.github.com> Date: Fri, 4 Oct 2024 22:36:41 -0500 Subject: [PATCH] Lich improvements -Various fixes -Lich enters third phase when all minions are dead -Added ranged attack to third phase -Added an extra shield -Restored life drain attack (WIP) --- .../client/model/entity/ModelTFLich.java | 5 + .../entity/ai/EntityAITFLichPopMobs.java | 66 + .../entity/ai/EntityAITFLichShootDagger.java | 94 ++ .../entity/boss/EntityTFLich.java | 1141 +++++++++-------- 4 files changed, 739 insertions(+), 567 deletions(-) create mode 100644 src/main/java/twilightforest/entity/ai/EntityAITFLichPopMobs.java create mode 100644 src/main/java/twilightforest/entity/ai/EntityAITFLichShootDagger.java diff --git a/src/main/java/twilightforest/client/model/entity/ModelTFLich.java b/src/main/java/twilightforest/client/model/entity/ModelTFLich.java index 531b778a86..181a0fb2b9 100644 --- a/src/main/java/twilightforest/client/model/entity/ModelTFLich.java +++ b/src/main/java/twilightforest/client/model/entity/ModelTFLich.java @@ -104,6 +104,11 @@ public void setRotationAngles(float limbSwing, float limbSwingAmount, float ageI bipedRightArm.rotateAngleX += MathHelper.sin(ageInTicks * 0.167F) * 0.15F; bipedLeftArm.rotateAngleX -= MathHelper.sin(ageInTicks * 0.167F) * 0.15F; + if (entity instanceof EntityTFLich && ((EntityTFLich) entity).isChargeDagger()){ + bipedRightArm.rotateAngleZ = -0.3F; + bipedLeftArm.rotateAngleX = 0.0F; + } + bipedHead.rotationPointY = -4.0F; bipedHeadwear.rotationPointY = -4.0F; bipedRightLeg.rotationPointY = 9.5F; diff --git a/src/main/java/twilightforest/entity/ai/EntityAITFLichPopMobs.java b/src/main/java/twilightforest/entity/ai/EntityAITFLichPopMobs.java new file mode 100644 index 0000000000..41b25cc094 --- /dev/null +++ b/src/main/java/twilightforest/entity/ai/EntityAITFLichPopMobs.java @@ -0,0 +1,66 @@ +package twilightforest.entity.ai; + +import java.util.List; + +import net.minecraft.entity.EntityLiving; +import net.minecraft.entity.ai.EntityAIBase; +import net.minecraft.entity.monster.EntityMob; +import net.minecraft.init.SoundEvents; +import net.minecraft.inventory.EntityEquipmentSlot; +import net.minecraft.item.ItemStack; +import net.minecraft.util.EnumHand; +import net.minecraft.util.math.AxisAlignedBB; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.WorldServer; +import twilightforest.entity.boss.EntityTFLich; +import twilightforest.item.TFItems; +import twilightforest.item.scepter.ItemTFScepterLifeDrain; + +public class EntityAITFLichPopMobs extends EntityAIBase { + + private final EntityTFLich lich; + + public EntityAITFLichPopMobs(EntityTFLich lich) { + this.lich = lich; + this.setMutexBits(3); + } + + @Override + public boolean shouldExecute() { + return !this.lich.isShadowClone() && + this.lich.getHealth() < this.lich.getMaxHealth() && + this.lich.getPopCooldown() == 0 && + !this.lich.world.getEntitiesWithinAABB(EntityLiving.class, + this.lich.getEntityBoundingBox().grow(32.0D, 16.0D, 32.0D), + e -> EntityTFLich.POPPABLE.contains(e.getClass()) && this.lich.getEntitySenses().canSee(e)).isEmpty(); + } + + @Override + public void startExecuting() { + lich.setItemStackToSlot(EntityEquipmentSlot.MAINHAND, new ItemStack(TFItems.lifedrain_scepter)); + } + + @Override + public void updateTask() { + super.updateTask(); + + // TODO: Add life drain scepter particles + for (EntityLiving mob : lich.world.getEntitiesWithinAABB(EntityLiving.class, this.lich.getEntityBoundingBox().grow(32.0D, 16.0D, 32.0D), e -> EntityTFLich.POPPABLE.contains(e.getClass()))) { + if (this.lich.getEntitySenses().canSee(mob)) { + mob.setDead(); + mob.spawnExplosionParticle(); + + // Play sounds + this.lich.playSound(SoundEvents.ENTITY_ZOMBIE_INFECT, 3.0F, 0.4F + this.lich.getRNG().nextFloat() * 0.2F); + mob.playSound(SoundEvents.ENTITY_ZOMBIE_VILLAGER_CURE, 0.7F, 2.0F + this.lich.getRNG().nextFloat() * 0.2F); + + // Heal 5% of max health + this.lich.heal(this.lich.getMaxHealth() * 5.0F / 100.0F); + + this.lich.swingArm(EnumHand.MAIN_HAND); + this.lich.setPopCooldown(40); + break; + } + } + } +} diff --git a/src/main/java/twilightforest/entity/ai/EntityAITFLichShootDagger.java b/src/main/java/twilightforest/entity/ai/EntityAITFLichShootDagger.java new file mode 100644 index 0000000000..9474352030 --- /dev/null +++ b/src/main/java/twilightforest/entity/ai/EntityAITFLichShootDagger.java @@ -0,0 +1,94 @@ +package twilightforest.entity.ai; + +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.ai.EntityAIBase; +import net.minecraft.init.Items; +import net.minecraft.init.SoundEvents; +import net.minecraft.inventory.EntityEquipmentSlot; +import net.minecraft.item.ItemStack; +import net.minecraft.util.math.MathHelper; +import twilightforest.entity.boss.EntityTFLich; +import twilightforest.entity.boss.EntityTFThrownWep; + +public class EntityAITFLichShootDagger extends EntityAIBase { + private EntityTFLich lich; + private int attackTick; + private EntityLivingBase attackTarget; + + public EntityAITFLichShootDagger(EntityTFLich entityTFLich) { + this.lich = entityTFLich; + this.setMutexBits(3); + } + + + @Override + public boolean shouldExecute() { + this.attackTarget = this.lich.getAttackTarget(); + + if (this.attackTarget == null) { + return false; + } else { + return this.lich.getPhase() == 3 && this.lich.getRNG().nextInt(40) == 0 && this.lich.canEntityBeSeen(this.attackTarget); + } + } + + @Override + public boolean shouldContinueExecuting() { + return this.attackTick >= 0; + } + + @Override + public void startExecuting() { + this.lich.setItemStackToSlot(EntityEquipmentSlot.MAINHAND, ItemStack.EMPTY); + attackTick = 5 + this.lich.getRNG().nextInt(5); + this.lich.setChargeDagger(true); + this.lich.playSound(SoundEvents.ITEM_ARMOR_EQUIP_GOLD, 2.0F, 1.1F); + + double d0 = this.attackTarget.posX - this.lich.posX; + double d1 = this.attackTarget.posZ - this.lich.posZ; + float f = MathHelper.sqrt(d0 * d0 + d1 * d1); + + if (this.lich.onGround) { + if ((double) f >= 1.0E-4D) { + this.lich.motionX -= d0 / (double) f * 0.8D * 0.7D + this.lich.motionX * 0.3D; + this.lich.motionZ -= d1 / (double) f * 0.8D * 0.7D + this.lich.motionZ * 0.3D; + } + + this.lich.motionY = 0.3F; + } + } + + @Override + public void updateTask() { + this.lich.getLookHelper().setLookPositionWithEntity(attackTarget, 30.0F, 30.0F); + + if (this.attackTick-- == 0) { + this.lich.setChargeDagger(false); + this.lich.playSound(SoundEvents.ENTITY_SNOWBALL_THROW, 2.0F, 1.0F + this.lich.getRNG().nextFloat() * 0.1F); + this.shootDagger(this.attackTarget); + } + } + + private void shootDagger(Entity targetedEntity) { + float bodyFacingAngle = ((this.lich.renderYawOffset * 3.141593F) / 180F); + double sx = this.lich.posX + (MathHelper.cos(bodyFacingAngle) * 1); + double sy = this.lich.posY + (this.lich.height * 0.82); + double sz = this.lich.posZ + (MathHelper.sin(bodyFacingAngle) * 1); + + double tx = targetedEntity.posX - sx; + double ty = (targetedEntity.getEntityBoundingBox().minY + (double) (targetedEntity.height / 2.0F)) - (this.lich.posY + this.lich.height / 2.0F); + double tz = targetedEntity.posZ - sz; + + this.lich.playSound(SoundEvents.ENTITY_ITEM_BREAK, 2.0F, 2.0F + this.lich.getRNG().nextFloat() * 0.1F); + EntityTFThrownWep projectile = new EntityTFThrownWep(this.lich.world, this.lich).setItem(new ItemStack(Items.GOLDEN_SWORD)); + + float speed = 1.5F; + + projectile.shoot(tx, ty, tz, speed, 1.0F); + + projectile.setLocationAndAngles(sx, sy, sz, this.lich.rotationYaw, this.lich.rotationPitch); + + this.lich.world.spawnEntity(projectile); + } +} diff --git a/src/main/java/twilightforest/entity/boss/EntityTFLich.java b/src/main/java/twilightforest/entity/boss/EntityTFLich.java index 5661211698..b2cd103b43 100644 --- a/src/main/java/twilightforest/entity/boss/EntityTFLich.java +++ b/src/main/java/twilightforest/entity/boss/EntityTFLich.java @@ -35,7 +35,10 @@ import twilightforest.block.TFBlocks; import twilightforest.entity.EntityTFSwarmSpider; import twilightforest.entity.ai.EntityAITFLichMinions; +import twilightforest.entity.ai.EntityAITFLichPopMobs; import twilightforest.entity.ai.EntityAITFLichShadows; +import twilightforest.entity.ai.EntityAITFLichShootDagger; +import twilightforest.entity.ai.EntityAITFLichWatchTarget; import twilightforest.enums.BossVariant; import twilightforest.world.TFWorld; @@ -45,571 +48,575 @@ public class EntityTFLich extends EntityMob { - public static final ResourceLocation LOOT_TABLE = TwilightForestMod.prefix("entities/lich"); - private static final Set> POPPABLE = ImmutableSet.of(EntitySkeleton.class, EntityZombie.class, EntityEnderman.class, EntitySpider.class, EntityCreeper.class, EntityTFSwarmSpider.class); - - private static final DataParameter DATA_ISCLONE = EntityDataManager.createKey(EntityTFLich.class, DataSerializers.BOOLEAN); - private static final DataParameter DATA_SHIELDSTRENGTH = EntityDataManager.createKey(EntityTFLich.class, DataSerializers.BYTE); - private static final DataParameter DATA_MINIONSLEFT = EntityDataManager.createKey(EntityTFLich.class, DataSerializers.BYTE); - private static final DataParameter DATA_ATTACKTYPE = EntityDataManager.createKey(EntityTFLich.class, DataSerializers.BYTE); - - public static final int MAX_SHADOW_CLONES = 2; - public static final int INITIAL_SHIELD_STRENGTH = 5; - public static final int MAX_ACTIVE_MINIONS = 3; - public static final int INITIAL_MINIONS_TO_SUMMON = 9; - public static final int MAX_HEALTH = 100; - - private EntityTFLich masterLich; - private int attackCooldown; - private final BossInfoServer bossInfo = new BossInfoServer(getDisplayName(), BossInfo.Color.YELLOW, BossInfo.Overlay.NOTCHED_6); - - public EntityTFLich(World world) { - super(world); - setSize(1.1F, 2.5F); - - setShadowClone(false); - this.masterLich = null; - this.isImmuneToFire = true; - this.experienceValue = 217; - } - - public EntityTFLich(World world, EntityTFLich otherLich) { - this(world); - - setShadowClone(true); - this.masterLich = otherLich; - } - - public EntityTFLich getMasterLich(){ - return masterLich; - } - - public int getAttackCooldown() { - return attackCooldown; - } - - public void setAttackCooldown(int cooldown) { - attackCooldown = cooldown; - } - - @Override - public void setCustomNameTag(String name) { - super.setCustomNameTag(name); - this.bossInfo.setName(this.getDisplayName()); - } - - @Override - protected void initEntityAI() { - this.tasks.addTask(0, new EntityAISwimming(this)); - this.tasks.addTask(1, new EntityAITFLichShadows(this)); - this.tasks.addTask(2, new EntityAITFLichMinions(this)); - this.tasks.addTask(3, new EntityAIAttackMelee(this, 1.0D, true) { - @Override - public boolean shouldExecute() { - return getPhase() == 3 && super.shouldExecute(); - } - - @Override - public void startExecuting() { - super.startExecuting(); - setItemStackToSlot(EntityEquipmentSlot.MAINHAND, new ItemStack(Items.GOLDEN_SWORD)); - } - }); - - this.targetTasks.addTask(1, new EntityAIHurtByTarget(this, false)); - this.targetTasks.addTask(2, new EntityAINearestAttackableTarget<>(this, EntityPlayer.class, false)); - } - - @Override - protected void entityInit() { - super.entityInit(); - dataManager.register(DATA_ISCLONE, false); - dataManager.register(DATA_SHIELDSTRENGTH, (byte) INITIAL_SHIELD_STRENGTH); - dataManager.register(DATA_MINIONSLEFT, (byte) INITIAL_MINIONS_TO_SUMMON); - dataManager.register(DATA_ATTACKTYPE, (byte) 0); - } - - @Override - protected void applyEntityAttributes() { - super.applyEntityAttributes(); - this.getEntityAttribute(SharedMonsterAttributes.MAX_HEALTH).setBaseValue(MAX_HEALTH); - this.getEntityAttribute(SharedMonsterAttributes.ATTACK_DAMAGE).setBaseValue(6.0D); - this.getEntityAttribute(SharedMonsterAttributes.MOVEMENT_SPEED).setBaseValue(0.45000001788139344D); // Same speed as an angry enderman - } - - @Override - public void addTrackingPlayer(EntityPlayerMP player) { - super.addTrackingPlayer(player); - this.bossInfo.addPlayer(player); - } - - @Override - public void removeTrackingPlayer(EntityPlayerMP player) { - super.removeTrackingPlayer(player); - this.bossInfo.removePlayer(player); - } - - @Override - public void setInWeb() { - } - - @Override - protected boolean canDespawn() { - return false; - } - - @Override - protected void despawnEntity() { - if (world.getDifficulty() == EnumDifficulty.PEACEFUL && !isShadowClone()) { - if (hasHome()) { - world.setBlockState(getHomePosition(), TFBlocks.boss_spawner.getDefaultState().withProperty(BlockTFBossSpawner.VARIANT, BossVariant.LICH)); - } - setDead(); - } else { - super.despawnEntity(); - } - } - - /** - * What phase of the fight are we on? - *

- * 1 - reflecting bolts, shield up - * 2 - summoning minions - * 3 - melee - */ - public int getPhase() { - if (isShadowClone() || getShieldStrength() > 0) { - return 1; - } else if (getMinionsToSummon() > 0) { - return 2; - } else { - return 3; - } - } - - @Override - public void onLivingUpdate() { - // determine the hand position - float angle = ((renderYawOffset * 3.141593F) / 180F); - - double dx = posX + (MathHelper.cos(angle) * 0.65); - double dy = posY + (height * 0.94); - double dz = posZ + (MathHelper.sin(angle) * 0.65); - - - // add particles! - - // how many particles do we want to add?! - int factor = (80 - attackCooldown) / 10; - int particles = factor > 0 ? rand.nextInt(factor) : 1; - - - for (int j1 = 0; j1 < particles; j1++) { - float sparkle = 1.0F - (attackCooldown + 1.0F) / 60.0F; - sparkle *= sparkle; - - float red = 0.37F * sparkle; - float grn = 0.99F * sparkle; - float blu = 0.89F * sparkle; - - // change color for fireball - if (this.getNextAttackType() != 0) { - red = 0.99F * sparkle; - grn = 0.47F * sparkle; - blu = 0.00F * sparkle; - } - - world.spawnParticle(EnumParticleTypes.SPELL_MOB, dx + (rand.nextGaussian() * 0.025), dy + (rand.nextGaussian() * 0.025), dz + (rand.nextGaussian() * 0.025), red, grn, blu); - } - - if (this.getPhase() == 3) - world.spawnParticle(EnumParticleTypes.VILLAGER_ANGRY, - this.posX + (double) (this.rand.nextFloat() * this.width * 2.0F) - (double) this.width, - this.posY + 1.0D + (double) (this.rand.nextFloat() * this.height), - this.posZ + (double) (this.rand.nextFloat() * this.width * 2.0F) - (double) this.width, - this.rand.nextGaussian() * 0.02D, this.rand.nextGaussian() * 0.02D, this.rand.nextGaussian() * 0.02D); - - if (!world.isRemote) { - if (this.getPhase() == 1) { - bossInfo.setPercent((float) (getShieldStrength() + 1) / (float) (INITIAL_SHIELD_STRENGTH + 1)); - } else { - bossInfo.setOverlay(BossInfo.Overlay.PROGRESS); - bossInfo.setPercent(getHealth() / getMaxHealth()); - if (this.getPhase() == 2) - bossInfo.setColor(BossInfo.Color.PURPLE); - else - bossInfo.setColor(BossInfo.Color.RED); - } - } - - super.onLivingUpdate(); - } - - @Override - public boolean attackEntityFrom(DamageSource src, float damage) { - // if we're in a wall, teleport for gosh sakes - if ("inWall".equals(src.getDamageType()) && getAttackTarget() != null) { - teleportToSightOfEntity(getAttackTarget()); - } - - if (isShadowClone() && src != DamageSource.OUT_OF_WORLD) { - playSound(SoundEvents.ENTITY_GENERIC_EXTINGUISH_FIRE, 1.0F, ((this.rand.nextFloat() - this.rand.nextFloat()) * 0.7F + 1.0F) * 2.0F); - return false; - } - - // ignore all bolts that are not reflected - if (src.getTrueSource() instanceof EntityTFLich) { - return false; - } - - // if our shield is up, ignore any damage that can be blocked. - if (src != DamageSource.OUT_OF_WORLD && getShieldStrength() > 0) { - if (src.isMagicDamage() && damage > 2) { - // reduce shield for magic damage greater than 1 heart - if (getShieldStrength() > 0) { - setShieldStrength(getShieldStrength() - 1); - playSound(SoundEvents.ENTITY_ITEM_BREAK, 1.0F, ((this.rand.nextFloat() - this.rand.nextFloat()) * 0.7F + 1.0F) * 2.0F); - } - } else { - playSound(SoundEvents.ENTITY_ITEM_BREAK, 1.0F, ((this.rand.nextFloat() - this.rand.nextFloat()) * 0.7F + 1.0F) * 2.0F); - if (src.getTrueSource() instanceof EntityLivingBase) { - setRevengeTarget((EntityLivingBase) src.getTrueSource()); - } - } - - return false; - } - - if (super.attackEntityFrom(src, damage)) { - // Prevent AIHurtByTarget from targeting our own companions - if (getRevengeTarget() instanceof EntityTFLich && ((EntityTFLich) getRevengeTarget()).masterLich == this.masterLich) { - setRevengeTarget(null); - } - - if (this.getPhase() < 3 || rand.nextInt(4) == 0) { - this.teleportToSightOfEntity(getAttackTarget()); - } - - return true; - } else { - return false; - } - } - - @Override - protected void updateAITasks() { - super.updateAITasks(); - - if (getAttackTarget() == null) { - return; - } - - if (attackCooldown > 0) { - attackCooldown--; - } - - // TODO: AI task? - if (!isShadowClone() && attackCooldown % 15 == 0) { - popNearbyMob(); - } - - // always watch our target - // TODO: make into AI task - this.getLookHelper().setLookPositionWithEntity(getAttackTarget(), 100F, 100F); - } - - public void launchBoltAt() { - float bodyFacingAngle = ((renderYawOffset * 3.141593F) / 180F); - double sx = posX + (MathHelper.cos(bodyFacingAngle) * 0.65); - double sy = posY + (height * 0.82); - double sz = posZ + (MathHelper.sin(bodyFacingAngle) * 0.65); - - double tx = getAttackTarget().posX - sx; - double ty = (getAttackTarget().getEntityBoundingBox().minY + (double) (getAttackTarget().height / 2.0F)) - (posY + height / 2.0F); - double tz = getAttackTarget().posZ - sz; - - playSound(SoundEvents.ENTITY_GHAST_SHOOT, getSoundVolume(), (rand.nextFloat() - rand.nextFloat()) * 0.2F + 1.0F); - - EntityTFLichBolt projectile = new EntityTFLichBolt(world, this); - projectile.setLocationAndAngles(sx, sy, sz, rotationYaw, rotationPitch); - projectile.shoot(tx, ty, tz, 0.5F, 1.0F); - - world.spawnEntity(projectile); - } - - public void launchBombAt() { - float bodyFacingAngle = ((renderYawOffset * 3.141593F) / 180F); - double sx = posX + (MathHelper.cos(bodyFacingAngle) * 0.65); - double sy = posY + (height * 0.82); - double sz = posZ + (MathHelper.sin(bodyFacingAngle) * 0.65); - - double tx = getAttackTarget().posX - sx; - double ty = (getAttackTarget().getEntityBoundingBox().minY + (double) (getAttackTarget().height / 2.0F)) - (posY + height / 2.0F); - double tz = getAttackTarget().posZ - sz; - - playSound(SoundEvents.ENTITY_GHAST_SHOOT, getSoundVolume(), (rand.nextFloat() - rand.nextFloat()) * 0.2F + 1.0F); - - EntityTFLichBomb projectile = new EntityTFLichBomb(world, this); - projectile.setLocationAndAngles(sx, sy, sz, rotationYaw, rotationPitch); - projectile.shoot(tx, ty, tz, 0.35F, 1.0F); - - world.spawnEntity(projectile); - } - - private void popNearbyMob() { - List nearbyMobs = world.getEntitiesWithinAABB(EntityLiving.class, new AxisAlignedBB(posX, posY, posZ, posX + 1, posY + 1, posZ + 1).grow(32.0D, 16.0D, 32.0D), e -> POPPABLE.contains(e.getClass())); - - for (EntityLiving mob : nearbyMobs) { - if (getEntitySenses().canSee(mob)) { - mob.spawnExplosionParticle(); - mob.setDead(); - // play death sound -// world.playSoundAtEntity(mob, mob.getDeathSound(), mob.getSoundVolume(), (rand.nextFloat() - rand.nextFloat()) * 0.2F + 1.0F); - - // make trail so it's clear that we did it - makeRedMagicTrail(mob.posX, mob.posY + mob.height / 2.0, mob.posZ, this.posX, this.posY + this.height / 2.0, this.posZ); - - break; - } - } - } - - public boolean wantsNewClone(EntityTFLich clone) { - return clone.isShadowClone() && countMyClones() < EntityTFLich.MAX_SHADOW_CLONES; - } - - public void setMaster(EntityTFLich lich) { - masterLich = lich; - } - - public int countMyClones() { - // check if there are enough clones. we check a 32x16x32 area - int count = 0; - - for (EntityTFLich nearbyLich : getNearbyLiches()) { - if (nearbyLich.isShadowClone() && nearbyLich.getMasterLich() == this) { - count++; - } - } - - return count; - } - - public List getNearbyLiches() { - return world.getEntitiesWithinAABB(getClass(), new AxisAlignedBB(posX, posY, posZ, posX + 1, posY + 1, posZ + 1).grow(32.0D, 16.0D, 32.0D)); - } - - public boolean wantsNewMinion(EntityTFLichMinion minion) { - return countMyMinions() < EntityTFLich.MAX_ACTIVE_MINIONS; - } - - public int countMyMinions() { - return (int) world.getEntitiesWithinAABB(EntityTFLichMinion.class, new AxisAlignedBB(posX, posY, posZ, posX + 1, posY + 1, posZ + 1).grow(32.0D, 16.0D, 32.0D)) - .stream() - .filter(m -> m.master == this) - .count(); - } - - public void teleportToSightOfEntity(Entity entity) { - Vec3d dest = findVecInLOSOf(entity); - double srcX = posX; - double srcY = posY; - double srcZ = posZ; - - if (dest != null) { - teleportToNoChecks(dest.x, dest.y, dest.z); - this.getLookHelper().setLookPositionWithEntity(entity, 100F, 100F); - this.renderYawOffset = this.rotationYaw; - - if (!this.getEntitySenses().canSee(entity)) { - teleportToNoChecks(srcX, srcY, srcZ); - } - } - } - - /** - * Returns coords that would be good to teleport to. - * Returns null if we can't find anything - */ - @Nullable - public Vec3d findVecInLOSOf(Entity targetEntity) { - if (targetEntity == null) return null; - double origX = posX; - double origY = posY; - double origZ = posZ; - - int tries = 100; - for (int i = 0; i < tries; i++) { - // we abuse EntityLivingBase.attemptTeleport, which does all the collision/ground checking for us, then teleport back to our original spot - double tx = targetEntity.posX + rand.nextGaussian() * 16D; - double ty = targetEntity.posY; - double tz = targetEntity.posZ + rand.nextGaussian() * 16D; - - boolean destClear = attemptTeleport(tx, ty, tz); - boolean canSeeTargetAtDest = canEntityBeSeen(targetEntity); // Don't use senses cache because we're in a temporary position - setPositionAndUpdate(origX, origY, origZ); - - if (destClear && canSeeTargetAtDest) { - return new Vec3d(tx, ty, tz); - } - } - - return null; - } - - /** - * Does not check that the teleport destination is valid, we just go there - */ - private void teleportToNoChecks(double destX, double destY, double destZ) { - // save original position - double srcX = posX; - double srcY = posY; - double srcZ = posZ; - - // change position - setPositionAndUpdate(destX, destY, destZ); - - makeTeleportTrail(srcX, srcY, srcZ, destX, destY, destZ); - this.world.playSound(null, srcX, srcY, srcZ, SoundEvents.ENTITY_ENDERMEN_TELEPORT, this.getSoundCategory(), 1.0F, 1.0F); - this.playSound(SoundEvents.ENTITY_ENDERMEN_TELEPORT, 1.0F, 1.0F); - - // sometimes we need to do this - this.isJumping = false; - } - - public void makeTeleportTrail(double srcX, double srcY, double srcZ, double destX, double destY, double destZ) { - // make particle trail - int particles = 128; - for (int i = 0; i < particles; i++) { - double trailFactor = i / (particles - 1.0D); - float f = (rand.nextFloat() - 0.5F) * 0.2F; - float f1 = (rand.nextFloat() - 0.5F) * 0.2F; - float f2 = (rand.nextFloat() - 0.5F) * 0.2F; - double tx = srcX + (destX - srcX) * trailFactor + (rand.nextDouble() - 0.5D) * width * 2D; - double ty = srcY + (destY - srcY) * trailFactor + rand.nextDouble() * height; - double tz = srcZ + (destZ - srcZ) * trailFactor + (rand.nextDouble() - 0.5D) * width * 2D; - world.spawnParticle(EnumParticleTypes.SPELL, tx, ty, tz, f, f1, f2); - } - } - - private void makeRedMagicTrail(double srcX, double srcY, double srcZ, double destX, double destY, double destZ) { - int particles = 32; - for (int i = 0; i < particles; i++) { - double trailFactor = i / (particles - 1.0D); - float f = 1.0F; - float f1 = 0.5F; - float f2 = 0.5F; - double tx = srcX + (destX - srcX) * trailFactor + rand.nextGaussian() * 0.005; - double ty = srcY + (destY - srcY) * trailFactor + rand.nextGaussian() * 0.005; - double tz = srcZ + (destZ - srcZ) * trailFactor + rand.nextGaussian() * 0.005; - world.spawnParticle(EnumParticleTypes.SPELL_MOB, tx, ty, tz, f, f1, f2); - } - } - - public void makeBlackMagicTrail(double srcX, double srcY, double srcZ, double destX, double destY, double destZ) { - // make particle trail - int particles = 32; - for (int i = 0; i < particles; i++) { - double trailFactor = i / (particles - 1.0D); - float f = 0.2F; - float f1 = 0.2F; - float f2 = 0.2F; - double tx = srcX + (destX - srcX) * trailFactor + rand.nextGaussian() * 0.005; - double ty = srcY + (destY - srcY) * trailFactor + rand.nextGaussian() * 0.005; - double tz = srcZ + (destZ - srcZ) * trailFactor + rand.nextGaussian() * 0.005; - world.spawnParticle(EnumParticleTypes.SPELL_MOB, tx, ty, tz, f, f1, f2); - } - } - - public boolean isShadowClone() { - return dataManager.get(DATA_ISCLONE); - } - - public void setShadowClone(boolean shadowClone) { - bossInfo.setVisible(!shadowClone); - dataManager.set(DATA_ISCLONE, shadowClone); - } - - public byte getShieldStrength() { - return dataManager.get(DATA_SHIELDSTRENGTH); - } - - public void setShieldStrength(int shieldStrength) { - dataManager.set(DATA_SHIELDSTRENGTH, (byte) shieldStrength); - } - - public byte getMinionsToSummon() { - return dataManager.get(DATA_MINIONSLEFT); - } - - public void setMinionsToSummon(int minionsToSummon) { - dataManager.set(DATA_MINIONSLEFT, (byte) minionsToSummon); - } - - public byte getNextAttackType() { - return dataManager.get(DATA_ATTACKTYPE); - } - - public void setNextAttackType(int attackType) { - dataManager.set(DATA_ATTACKTYPE, (byte) attackType); - } - - @Override - protected SoundEvent getAmbientSound() { - return SoundEvents.ENTITY_BLAZE_AMBIENT; - } - - @Override - protected SoundEvent getHurtSound(DamageSource source) { - return SoundEvents.ENTITY_BLAZE_HURT; - } - - @Override - protected SoundEvent getDeathSound() { - return SoundEvents.ENTITY_BLAZE_DEATH; - } - - @Override - public ResourceLocation getLootTable() { - return !isShadowClone() ? LOOT_TABLE : null; - } - - @Override - public void writeEntityToNBT(NBTTagCompound compound) { - super.writeEntityToNBT(compound); - compound.setBoolean("ShadowClone", isShadowClone()); - compound.setByte("ShieldStrength", getShieldStrength()); - compound.setByte("MinionsToSummon", getMinionsToSummon()); - } - - @Override - public void readEntityFromNBT(NBTTagCompound compound) { - super.readEntityFromNBT(compound); - setShadowClone(compound.getBoolean("ShadowClone")); - setShieldStrength(compound.getByte("ShieldStrength")); - setMinionsToSummon(compound.getByte("MinionsToSummon")); - if (this.hasCustomName()) { - this.bossInfo.setName(this.getDisplayName()); - } - } - - @Override - public void onDeath(DamageSource cause) { - super.onDeath(cause); - // mark the tower as defeated - if (!world.isRemote && !this.isShadowClone()) { - this.bossInfo.setPercent(0.0F); - TFWorld.markStructureConquered(world, new BlockPos(this), TFFeature.LICH_TOWER); - } - } - - @Override - public EnumCreatureAttribute getCreatureAttribute() { - return EnumCreatureAttribute.UNDEAD; - } - - @Override - public boolean isNonBoss() { - return false; - } + public static final ResourceLocation LOOT_TABLE = TwilightForestMod.prefix("entities/lich"); + public static final Set> POPPABLE = ImmutableSet.of(EntitySkeleton.class, EntityZombie.class, EntityEnderman.class, EntitySpider.class, EntityCreeper.class, EntityTFSwarmSpider.class); + + private static final DataParameter DATA_CHARGEDAGGER = EntityDataManager.createKey(EntityTFLich.class, DataSerializers.BOOLEAN); + private static final DataParameter DATA_ISCLONE = EntityDataManager.createKey(EntityTFLich.class, DataSerializers.BOOLEAN); + private static final DataParameter DATA_SHIELDSTRENGTH = EntityDataManager.createKey(EntityTFLich.class, DataSerializers.BYTE); + private static final DataParameter DATA_MINIONSLEFT = EntityDataManager.createKey(EntityTFLich.class, DataSerializers.BYTE); + private static final DataParameter DATA_ATTACKTYPE = EntityDataManager.createKey(EntityTFLich.class, DataSerializers.BYTE); + + public static final int MAX_SHADOW_CLONES = 2; + public static final int INITIAL_SHIELD_STRENGTH = 6; + public static final int MAX_ACTIVE_MINIONS = 3; + public static final int INITIAL_MINIONS_TO_SUMMON = 9; + public static final int MAX_HEALTH = 100; + + private EntityTFLich masterLich; + private int attackCooldown; + private int popCooldown; + private final BossInfoServer bossInfo = new BossInfoServer(getDisplayName(), BossInfo.Color.YELLOW, BossInfo.Overlay.NOTCHED_6); + + public EntityTFLich(World world) { + super(world); + setSize(1.1F, 2.5F); + + setShadowClone(false); + this.masterLich = null; + this.isImmuneToFire = true; + this.experienceValue = 217; + } + + public EntityTFLich(World world, EntityTFLich otherLich) { + this(world); + + setShadowClone(true); + this.masterLich = otherLich; + } + + public EntityTFLich getMasterLich() { + return masterLich; + } + + public int getAttackCooldown() { + return attackCooldown; + } + + public void setAttackCooldown(int cooldown) { + attackCooldown = cooldown; + } + + public int getPopCooldown() { + return popCooldown; + } + + public void setPopCooldown(int cooldown) { + popCooldown = cooldown; + } + + @Override + public void setCustomNameTag(String name) { + super.setCustomNameTag(name); + this.bossInfo.setName(this.getDisplayName()); + } + + @Override + protected void initEntityAI() { + this.tasks.addTask(0, new EntityAISwimming(this)); + this.tasks.addTask(1, new EntityAITFLichWatchTarget(this)); + this.tasks.addTask(1, new EntityAITFLichPopMobs(this)); + this.tasks.addTask(2, new EntityAITFLichShadows(this)); + this.tasks.addTask(3, new EntityAITFLichMinions(this)); + this.tasks.addTask(4, new EntityAITFLichShootDagger(this)); + this.tasks.addTask(5, new EntityAIAttackMelee(this, 0.75D, true) { + @Override + public boolean shouldExecute() { + return getPhase() == 3 && super.shouldExecute(); + } + + @Override + public void startExecuting() { + super.startExecuting(); + setItemStackToSlot(EntityEquipmentSlot.MAINHAND, new ItemStack(Items.GOLDEN_SWORD)); + } + }); + + this.targetTasks.addTask(1, new EntityAIHurtByTarget(this, false)); + this.targetTasks.addTask(2, new EntityAINearestAttackableTarget<>(this, EntityPlayer.class, false)); + } + + @Override + protected void entityInit() { + super.entityInit(); + dataManager.register(DATA_CHARGEDAGGER, false); + dataManager.register(DATA_ISCLONE, false); + dataManager.register(DATA_SHIELDSTRENGTH, (byte) INITIAL_SHIELD_STRENGTH); + dataManager.register(DATA_MINIONSLEFT, (byte) INITIAL_MINIONS_TO_SUMMON); + dataManager.register(DATA_ATTACKTYPE, (byte) 0); + } + + @Override + protected void applyEntityAttributes() { + super.applyEntityAttributes(); + this.getEntityAttribute(SharedMonsterAttributes.MAX_HEALTH).setBaseValue(MAX_HEALTH); + this.getEntityAttribute(SharedMonsterAttributes.ATTACK_DAMAGE).setBaseValue(6.0D); + this.getEntityAttribute(SharedMonsterAttributes.MOVEMENT_SPEED).setBaseValue(0.45000001788139344D); // Same speed as an angry enderman + } + + @Override + public void addTrackingPlayer(EntityPlayerMP player) { + super.addTrackingPlayer(player); + this.bossInfo.addPlayer(player); + } + + @Override + public void removeTrackingPlayer(EntityPlayerMP player) { + super.removeTrackingPlayer(player); + this.bossInfo.removePlayer(player); + } + + @Override + public void setInWeb() { + } + + @Override + protected boolean canDespawn() { + return false; + } + + @Override + protected void despawnEntity() { + if (world.getDifficulty() == EnumDifficulty.PEACEFUL && !isShadowClone()) { + if (hasHome()) { + world.setBlockState(getHomePosition(), TFBlocks.boss_spawner.getDefaultState().withProperty(BlockTFBossSpawner.VARIANT, BossVariant.LICH)); + } + setDead(); + } else { + super.despawnEntity(); + } + } + + /** + * What phase of the fight are we on? + *

+ * 1 - reflecting bolts, shield up + * 2 - summoning minions + * 3 - melee + */ + public int getPhase() { + if (isShadowClone() || getShieldStrength() > 0) { + return 1; + } else if (getMinionsToSummon() > 0 || countMyMinions() > 0) { + return 2; + } else { + return 3; + } + } + + @Override + public void onLivingUpdate() { + // determine the hand position + float angle = ((renderYawOffset * 3.141593F) / 180F); + + double dx = posX + (MathHelper.cos(angle) * 0.65); + double dy = posY + (height * 0.94); + double dz = posZ + (MathHelper.sin(angle) * 0.65); + + + // add particles! + + // how many particles do we want to add?! + int factor = (80 - attackCooldown) / 10; + int particles = factor > 0 ? rand.nextInt(factor) : 1; + + + for (int j1 = 0; j1 < particles; j1++) { + float sparkle = 1.0F - (attackCooldown + 1.0F) / 60.0F; + sparkle *= sparkle; + + float red = 0.37F * sparkle; + float grn = 0.99F * sparkle; + float blu = 0.89F * sparkle; + + // change color for fireball + if (this.getNextAttackType() != 0) { + red = 0.99F * sparkle; + grn = 0.47F * sparkle; + blu = 0.00F * sparkle; + } + + world.spawnParticle(EnumParticleTypes.SPELL_MOB, dx + (rand.nextGaussian() * 0.025), dy + (rand.nextGaussian() * 0.025), dz + (rand.nextGaussian() * 0.025), red, grn, blu); + } + + if (this.getPhase() == 3) + world.spawnParticle(EnumParticleTypes.VILLAGER_ANGRY, + this.posX + (double) (this.rand.nextFloat() * this.width * 2.0F) - (double) this.width, + this.posY + 1.0D + (double) (this.rand.nextFloat() * this.height), + this.posZ + (double) (this.rand.nextFloat() * this.width * 2.0F) - (double) this.width, + this.rand.nextGaussian() * 0.02D, this.rand.nextGaussian() * 0.02D, this.rand.nextGaussian() * 0.02D); + + if (!world.isRemote) { + if (this.getPhase() == 1) { + bossInfo.setPercent((float) (getShieldStrength()) / (float) (INITIAL_SHIELD_STRENGTH)); + } else { + bossInfo.setOverlay(BossInfo.Overlay.PROGRESS); + bossInfo.setPercent(getHealth() / getMaxHealth()); + if (this.getPhase() == 2) + bossInfo.setColor(BossInfo.Color.PURPLE); + else + bossInfo.setColor(BossInfo.Color.RED); + } + } + + super.onLivingUpdate(); + } + + @Override + public boolean attackEntityFrom(DamageSource src, float damage) { + // if we're in a wall, teleport for gosh sakes + if ("inWall".equals(src.getDamageType()) && getAttackTarget() != null) { + teleportToSightOfEntity(getAttackTarget()); + } + + if (isShadowClone() && src != DamageSource.OUT_OF_WORLD) { + playSound(SoundEvents.ENTITY_GENERIC_EXTINGUISH_FIRE, 1.0F, ((this.rand.nextFloat() - this.rand.nextFloat()) * 0.7F + 1.0F) * 2.0F); + return false; + } + + // ignore all bolts that are not reflected + if (src.getTrueSource() instanceof EntityTFLich) { + return false; + } + + // if our shield is up, ignore any damage that can be blocked. + if (src != DamageSource.OUT_OF_WORLD && getShieldStrength() > 0) { + if (src.isMagicDamage() && damage > 2) { + // reduce shield for magic damage greater than 1 heart + if (getShieldStrength() > 0) { + setShieldStrength(getShieldStrength() - 1); + playSound(SoundEvents.ENTITY_ITEM_BREAK, 1.0F, ((this.rand.nextFloat() - this.rand.nextFloat()) * 0.7F + 1.0F) * 2.0F); + } + } else { + playSound(SoundEvents.ENTITY_ITEM_BREAK, 1.0F, ((this.rand.nextFloat() - this.rand.nextFloat()) * 0.7F + 1.0F) * 2.0F); + if (src.getTrueSource() instanceof EntityLivingBase) { + setRevengeTarget((EntityLivingBase) src.getTrueSource()); + } + } + + return false; + } + + if (super.attackEntityFrom(src, damage)) { + // Prevent AIHurtByTarget from targeting our own companions + if (getRevengeTarget() instanceof EntityTFLich && ((EntityTFLich) getRevengeTarget()).masterLich == this.masterLich) { + setRevengeTarget(null); + } + + if (this.getPhase() < 3 || rand.nextInt(4) == 0) { + this.teleportToSightOfEntity(getAttackTarget()); + } + + return true; + } else { + return false; + } + } + + @Override + protected void updateAITasks() { + super.updateAITasks(); + + if (getAttackTarget() == null) { + return; + } + + if (attackCooldown > 0) { + attackCooldown--; + } + + if (popCooldown > 0) { + popCooldown--; + } + } + + public void launchBoltAt() { + float bodyFacingAngle = ((renderYawOffset * 3.141593F) / 180F); + double sx = posX + (MathHelper.cos(bodyFacingAngle) * 0.65); + double sy = posY + (height * 0.82); + double sz = posZ + (MathHelper.sin(bodyFacingAngle) * 0.65); + + double tx = getAttackTarget().posX - sx; + double ty = (getAttackTarget().getEntityBoundingBox().minY + (double) (getAttackTarget().height / 2.0F)) - (posY + height / 2.0F); + double tz = getAttackTarget().posZ - sz; + + playSound(SoundEvents.ENTITY_GHAST_SHOOT, getSoundVolume(), (rand.nextFloat() - rand.nextFloat()) * 0.2F + 1.0F); + + EntityTFLichBolt projectile = new EntityTFLichBolt(world, this); + projectile.setLocationAndAngles(sx, sy, sz, rotationYaw, rotationPitch); + projectile.shoot(tx, ty, tz, 0.5F, 1.0F); + + world.spawnEntity(projectile); + } + + public void launchBombAt() { + float bodyFacingAngle = ((renderYawOffset * 3.141593F) / 180F); + double sx = posX + (MathHelper.cos(bodyFacingAngle) * 0.65); + double sy = posY + (height * 0.82); + double sz = posZ + (MathHelper.sin(bodyFacingAngle) * 0.65); + + double tx = getAttackTarget().posX - sx; + double ty = (getAttackTarget().getEntityBoundingBox().minY + (double) (getAttackTarget().height / 2.0F)) - (posY + height / 2.0F); + double tz = getAttackTarget().posZ - sz; + + playSound(SoundEvents.ENTITY_GHAST_SHOOT, getSoundVolume(), (rand.nextFloat() - rand.nextFloat()) * 0.2F + 1.0F); + + EntityTFLichBomb projectile = new EntityTFLichBomb(world, this); + projectile.setLocationAndAngles(sx, sy, sz, rotationYaw, rotationPitch); + projectile.shoot(tx, ty, tz, 0.35F, 1.0F); + + world.spawnEntity(projectile); + } + + public boolean wantsNewClone(EntityTFLich clone) { + return clone.isShadowClone() && countMyClones() < EntityTFLich.MAX_SHADOW_CLONES; + } + + public void setMaster(EntityTFLich lich) { + masterLich = lich; + } + + public int countMyClones() { + // check if there are enough clones. we check a 32x16x32 area + int count = 0; + + for (EntityTFLich nearbyLich : getNearbyLiches()) { + if (nearbyLich.isShadowClone() && nearbyLich.getMasterLich() == this) { + count++; + } + } + + return count; + } + + public List getNearbyLiches() { + return world.getEntitiesWithinAABB(getClass(), new AxisAlignedBB(posX, posY, posZ, posX + 1, posY + 1, posZ + 1).grow(32.0D, 16.0D, 32.0D)); + } + + public boolean wantsNewMinion(EntityTFLichMinion minion) { + return countMyMinions() < EntityTFLich.MAX_ACTIVE_MINIONS; + } + + public int countMyMinions() { + return (int) world.getEntitiesWithinAABB(EntityTFLichMinion.class, new AxisAlignedBB(posX, posY, posZ, posX + 1, posY + 1, posZ + 1).grow(32.0D, 16.0D, 32.0D)) + .stream() + .filter(m -> m.master == this) + .count(); + } + + public void teleportToSightOfEntity(Entity entity) { + Vec3d dest = findVecInLOSOf(entity); + double srcX = posX; + double srcY = posY; + double srcZ = posZ; + + if (dest != null && entity != null) { + teleportToNoChecks(dest.x, dest.y, dest.z); + this.getLookHelper().setLookPositionWithEntity(entity, 100F, 100F); + this.renderYawOffset = this.rotationYaw; + + if (!this.getEntitySenses().canSee(entity)) { + teleportToNoChecks(srcX, srcY, srcZ); + } + } + } + + /** + * Returns coords that would be good to teleport to. + * Returns null if we can't find anything + */ + @Nullable + public Vec3d findVecInLOSOf(@Nullable Entity targetEntity) { + if (targetEntity == null) return null; + double origX = posX; + double origY = posY; + double origZ = posZ; + + int tries = 100; + for (int i = 0; i < tries; i++) { + // we abuse EntityLivingBase.attemptTeleport, which does all the collision/ground checking for us, then teleport back to our original spot + double tx = targetEntity.posX + rand.nextGaussian() * 16D; + double ty = targetEntity.posY; + double tz = targetEntity.posZ + rand.nextGaussian() * 16D; + + boolean destClear = attemptTeleport(tx, ty, tz); + boolean canSeeTargetAtDest = canEntityBeSeen(targetEntity); // Don't use senses cache because we're in a temporary position + setPositionAndUpdate(origX, origY, origZ); + + if (destClear && canSeeTargetAtDest) { + return new Vec3d(tx, ty, tz); + } + } + + return null; + } + + /** + * Does not check that the teleport destination is valid, we just go there + */ + private void teleportToNoChecks(double destX, double destY, double destZ) { + // save original position + double srcX = posX; + double srcY = posY; + double srcZ = posZ; + + // change position + setPositionAndUpdate(destX, destY, destZ); + + makeTeleportTrail(srcX, srcY, srcZ, destX, destY, destZ); + this.world.playSound(null, srcX, srcY, srcZ, SoundEvents.ENTITY_ENDERMEN_TELEPORT, this.getSoundCategory(), 1.0F, 1.0F); + this.playSound(SoundEvents.ENTITY_ENDERMEN_TELEPORT, 1.0F, 1.0F); + + // sometimes we need to do this + this.isJumping = false; + } + + public void makeTeleportTrail(double srcX, double srcY, double srcZ, double destX, double destY, double destZ) { + // make particle trail + int particles = 128; + for (int i = 0; i < particles; i++) { + double trailFactor = i / (particles - 1.0D); + float f = (rand.nextFloat() - 0.5F) * 0.2F; + float f1 = (rand.nextFloat() - 0.5F) * 0.2F; + float f2 = (rand.nextFloat() - 0.5F) * 0.2F; + double tx = srcX + (destX - srcX) * trailFactor + (rand.nextDouble() - 0.5D) * width * 2D; + double ty = srcY + (destY - srcY) * trailFactor + rand.nextDouble() * height; + double tz = srcZ + (destZ - srcZ) * trailFactor + (rand.nextDouble() - 0.5D) * width * 2D; + world.spawnParticle(EnumParticleTypes.SPELL, tx, ty, tz, f, f1, f2); + } + } + + public void makeRedMagicTrail(double srcX, double srcY, double srcZ, double destX, double destY, double destZ) { + int particles = 60; + for (int i = 0; i < particles; i++) { + double trailFactor = i / (particles - 1.0D); + float f = 1.0F; + float f1 = 0.5F; + float f2 = 0.5F; + double tx = srcX + (destX - srcX) * trailFactor + rand.nextGaussian() * 0.005; + double ty = srcY + (destY - srcY) * trailFactor + rand.nextGaussian() * 0.005; + double tz = srcZ + (destZ - srcZ) * trailFactor + rand.nextGaussian() * 0.005; + world.spawnParticle(EnumParticleTypes.SPELL_MOB, tx, ty, tz, f, f1, f2); + } + } + + public void makeBlackMagicTrail(double srcX, double srcY, double srcZ, double destX, double destY, double destZ) { + // make particle trail + int particles = 60; + for (int i = 0; i < particles; i++) { + double trailFactor = i / (particles - 1.0D); + float f = 0.2F; + float f1 = 0.2F; + float f2 = 0.2F; + double tx = srcX + (destX - srcX) * trailFactor + rand.nextGaussian() * 0.005; + double ty = srcY + (destY - srcY) * trailFactor + rand.nextGaussian() * 0.005; + double tz = srcZ + (destZ - srcZ) * trailFactor + rand.nextGaussian() * 0.005; + world.spawnParticle(EnumParticleTypes.SPELL_MOB, tx, ty, tz, f, f1, f2); + } + } + + public boolean isChargeDagger() { + return dataManager.get(DATA_CHARGEDAGGER); + } + + public void setChargeDagger(boolean chargeDagger) { + dataManager.set(DATA_CHARGEDAGGER, chargeDagger); + } + + public boolean isShadowClone() { + return dataManager.get(DATA_ISCLONE); + } + + public void setShadowClone(boolean shadowClone) { + bossInfo.setVisible(!shadowClone); + dataManager.set(DATA_ISCLONE, shadowClone); + } + + public byte getShieldStrength() { + return dataManager.get(DATA_SHIELDSTRENGTH); + } + + public void setShieldStrength(int shieldStrength) { + dataManager.set(DATA_SHIELDSTRENGTH, (byte) shieldStrength); + } + + public byte getMinionsToSummon() { + return dataManager.get(DATA_MINIONSLEFT); + } + + public void setMinionsToSummon(int minionsToSummon) { + dataManager.set(DATA_MINIONSLEFT, (byte) minionsToSummon); + } + + public byte getNextAttackType() { + return dataManager.get(DATA_ATTACKTYPE); + } + + public void setNextAttackType(int attackType) { + dataManager.set(DATA_ATTACKTYPE, (byte) attackType); + } + + @Override + protected SoundEvent getAmbientSound() { + return SoundEvents.ENTITY_BLAZE_AMBIENT; + } + + @Override + protected SoundEvent getHurtSound(DamageSource source) { + return SoundEvents.ENTITY_BLAZE_HURT; + } + + @Override + protected SoundEvent getDeathSound() { + return SoundEvents.ENTITY_BLAZE_DEATH; + } + + @Override + public ResourceLocation getLootTable() { + return !isShadowClone() ? LOOT_TABLE : null; + } + + @Override + public void writeEntityToNBT(NBTTagCompound compound) { + super.writeEntityToNBT(compound); + compound.setBoolean("ShadowClone", isShadowClone()); + compound.setByte("ShieldStrength", getShieldStrength()); + compound.setByte("MinionsToSummon", getMinionsToSummon()); + } + + @Override + public void readEntityFromNBT(NBTTagCompound compound) { + super.readEntityFromNBT(compound); + setShadowClone(compound.getBoolean("ShadowClone")); + setShieldStrength(compound.getByte("ShieldStrength")); + setMinionsToSummon(compound.getByte("MinionsToSummon")); + if (this.hasCustomName()) { + this.bossInfo.setName(this.getDisplayName()); + } + } + + @Override + public void onDeath(DamageSource cause) { + super.onDeath(cause); + // mark the tower as defeated + if (!world.isRemote && !this.isShadowClone()) { + this.bossInfo.setPercent(0.0F); + TFWorld.markStructureConquered(world, new BlockPos(this), TFFeature.LICH_TOWER); + } + } + + @Override + public EnumCreatureAttribute getCreatureAttribute() { + return EnumCreatureAttribute.UNDEAD; + } + + @Override + protected boolean canBeRidden(Entity entity) { + return false; + } + + @Override + public boolean isNonBoss() { + return false; + } }