package com.hbm.entity.mob; import java.util.Arrays; import java.util.Collections; import java.util.List; import com.hbm.blocks.ModBlocks; import com.hbm.config.MobConfig; import com.hbm.entity.logic.EntityWaypoint; import com.hbm.entity.pathfinder.PathFinderUtils; import com.hbm.explosion.vanillant.ExplosionVNT; import com.hbm.explosion.vanillant.standard.*; import com.hbm.handler.pollution.PollutionHandler; import com.hbm.handler.pollution.PollutionHandler.PollutionType; import com.hbm.items.ModItems; import com.hbm.lib.ModDamageSource; import com.hbm.main.ResourceManager; import com.hbm.potion.HbmPotion; import net.minecraft.block.Block; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.EnumCreatureAttribute; import net.minecraft.entity.SharedMonsterAttributes; import net.minecraft.entity.monster.EntityMob; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.*; import net.minecraft.potion.Potion; import net.minecraft.potion.PotionEffect; import net.minecraft.util.DamageSource; import net.minecraft.util.ResourceLocation; import net.minecraft.world.EnumDifficulty; import net.minecraft.world.World; import javax.annotation.Nullable; public class EntityGlyphid extends EntityMob { //I might have overdone it a little bit public boolean hasHome = false; public int homeX; public int homeY; public int homeZ; protected int currentTask = 0; //both of those below are used for digging, so the glyphid remembers what it was doing protected int previousTask; protected EntityWaypoint previousWaypoint; public int taskX; public int taskY; public int taskZ; //used for digging, bigger glyphids have a longer reach public int blastSize = Math.min((int) (3 * (getScale())) / 2, 5); public int blastResToDig = Math.min((int) (50 * (getScale() * 2)), 150); public boolean shouldDig; // Tasks /** Idle state, only makes glpyhids wander around randomly */ public static final int TASK_IDLE = 0; /** Causes the glyphid to walk to the waypoint, then communicate the FOLLOW task to nearby glyphids */ public static final int TASK_RETREAT_FOR_REINFORCEMENTS = 1; /** Task used by scouts, if the waypoint is reached it will construct a new hive */ public static final int TASK_BUILD_HIVE = 2; /** Creates a waypoint at the home position and then immediately initiates the RETREAT_FOR_REINFORCEMENTS task */ public static final int TASK_INITIATE_RETREAT = 3; /** Will simply walk to the waypoint and enter IDLE once it is reached */ public static final int TASK_FOLLOW = 4; /** Causes nuclear glyphids to immediately self-destruct, also signaling nearby scouts to retreat */ public static final int TASK_TERRAFORM = 5; /** If any task other than IDLE is interrupted by an obstacle, initiates digging behavior which is also communicated to nearby glyohids */ public static final int TASK_DIG = 6; protected boolean hasWaypoint = false; /** Yeah, fuck, whatever, anything goes now */ protected EntityWaypoint taskWaypoint = null; //subtypes public static final int TYPE_NORMAL = 0; public static final int TYPE_INFECTED = 1; //data watcher keys public static final int DW_WALL = 16; public static final int DW_ARMOR = 17; public static final int DW_SUBTYPE = 18; public EntityGlyphid(World world) { super(world); this.setSize(1.75F, 1F); } public ResourceLocation getSkin() { return ResourceManager.glyphid_tex; } public double getScale() { return 1.0D; } @Override protected void entityInit() { super.entityInit(); this.dataWatcher.addObject(DW_WALL, new Byte((byte) 0)); //wall climbing this.dataWatcher.addObject(DW_ARMOR, new Byte((byte) 0b11111)); //armor this.dataWatcher.addObject(DW_SUBTYPE, new Byte((byte) 0)); //subtype (i.e. normal, infected, etc) } @Override protected void applyEntityAttributes() { super.applyEntityAttributes(); this.getEntityAttribute(SharedMonsterAttributes.maxHealth).setBaseValue(30D); this.getEntityAttribute(SharedMonsterAttributes.movementSpeed).setBaseValue(1D); this.getEntityAttribute(SharedMonsterAttributes.attackDamage).setBaseValue(5D); } @Override public void onUpdate() { super.onUpdate(); if(!worldObj.isRemote) { if(!hasHome) { homeX = (int) posX; homeY = (int) posY; homeZ = (int) posZ; hasHome = true; } if(this.isPotionActive(Potion.blindness)) { onBlinded(); } if(getCurrentTask() == TASK_FOLLOW){ //incase the waypoint somehow doesn't exist and it got this task anyway if(isAtDestination() && !hasWaypoint) { setCurrentTask(TASK_IDLE, null); } //the task cannot be 6 outside of rampant, so this is a non issue p much } else if (getCurrentTask() == TASK_DIG && ticksExisted % 20 == 0 && isAtDestination()) { swingItem(); ExplosionVNT vnt = new ExplosionVNT(worldObj, taskX, taskY + 2, taskZ, blastSize, this); vnt.setBlockAllocator(new BlockAllocatorGlyphidDig(blastResToDig)); vnt.setBlockProcessor(new BlockProcessorStandard().setNoDrop()); vnt.setEntityProcessor(null); vnt.setPlayerProcessor(null); vnt.explode(); this.setCurrentTask(previousTask, previousWaypoint); } this.setBesideClimbableBlock(isCollidedHorizontally); if(ticksExisted % 100 == 0) { this.swingItem(); } } } @Override protected void dropFewItems(boolean byPlayer, int looting) { super.dropFewItems(byPlayer, looting); Item drop = isBurning() ? ModItems.glyphid_meat_grilled : ModItems.glyphid_meat; if(rand.nextInt(2) == 0) this.entityDropItem(new ItemStack(drop, ((int) getScale() * 2) + looting), 0F); } @Override protected Entity findPlayerToAttack() { if(this.isPotionActive(Potion.blindness)) return null; return this.worldObj.getClosestVulnerablePlayerToEntity(this, useExtendedTargeting() ? 128D : 16D); } @Override protected void updateWanderPath() { if(getCurrentTask() == TASK_IDLE) { super.updateWanderPath(); } } @Override protected void updateEntityActionState() { super.updateEntityActionState(); if(!this.isPotionActive(Potion.blindness)) { if (!this.hasPath()) { // hell yeah!! if(useExtendedTargeting() && this.entityToAttack != null) { this.setPathToEntity(PathFinderUtils.getPathEntityToEntityPartial(worldObj, this, this.entityToAttack, 16F, true, false, true, true)); } else if (getCurrentTask() != TASK_IDLE) { this.worldObj.theProfiler.startSection("stroll"); if (!isAtDestination()) { if (taskWaypoint != null) { taskX = (int) taskWaypoint.posX; taskY = (int) taskWaypoint.posY; taskZ = (int) taskWaypoint.posZ; if (taskWaypoint.highPriority) { setTarget(taskWaypoint); } } if(hasWaypoint) { if(canDig()) { MovingObjectPosition obstacle = findWaypointObstruction(); if (getScale() >= 1 && getCurrentTask() != TASK_DIG && obstacle != null) { digToWaypoint(obstacle); } else { Vec3 vec = Vec3.createVectorHelper(posX, posY, posZ); int maxDist = (int) (Math.sqrt(vec.squareDistanceTo(taskX, taskY, taskZ)) * 1.2); this.setPathToEntity(PathFinderUtils.getPathEntityToCoordPartial(worldObj, this, taskX, taskY, taskZ, maxDist, true, false, true, true)); } } else { Vec3 vec = Vec3.createVectorHelper(posX, posY, posZ); int maxDist = (int) (Math.sqrt(vec.squareDistanceTo(taskX, taskY, taskZ)) * 1.2); this.setPathToEntity(PathFinderUtils.getPathEntityToCoordPartial(worldObj, this, taskX, taskY, taskZ, maxDist, true, false, true, true)); } } } this.worldObj.theProfiler.endSection(); } } } } protected boolean canDig() { return MobConfig.rampantDig; } public void onBlinded(){ this.entityToAttack = null; this.setPathToEntity(null); this.fleeingTick = 80; if(getScale() >= 1.25){ if(ticksExisted % 20 == 0) { for (int i = 0; i < 16; i++) { float angle = (float) Math.toRadians(360D / 16 * i); Vec3 rot = Vec3.createVectorHelper(0, 0, 4); rot.rotateAroundY(angle); Vec3 pos = Vec3.createVectorHelper(this.posX, this.posY + 1, this.posZ); Vec3 nextPos = Vec3.createVectorHelper(this.posX + rot.xCoord, this.posY + 1, this.posZ + rot.zCoord); MovingObjectPosition mop = this.worldObj.rayTraceBlocks(pos, nextPos); if (mop != null && mop.typeOfHit == mop.typeOfHit.BLOCK) { Block block = worldObj.getBlock(mop.blockX, mop.blockY, mop.blockZ); if (block == ModBlocks.lantern) { rotationYaw = 360F / 16 * i; swingItem(); worldObj.func_147480_a(mop.blockX, mop.blockY, mop.blockZ, false); } } } } } } public boolean useExtendedTargeting() { return MobConfig.rampantExtendedTargetting || PollutionHandler.getPollution(worldObj, (int) Math.floor(posX), (int) Math.floor(posY), (int) Math.floor(posZ), PollutionType.SOOT) >= MobConfig.targetingThreshold; } @Override protected boolean canDespawn() { return entityToAttack == null && getCurrentTask() == TASK_IDLE && this.ticksExisted > 100; } @Override public boolean attackEntityFrom(DamageSource source, float amount) { if(source.getEntity() instanceof EntityGlyphid) { return false; } if(!source.isDamageAbsolute() && !source.isUnblockable() && !worldObj.isRemote && !source.isFireDamage() && !source.getDamageType().equals(ModDamageSource.s_cryolator)) { byte armor = this.dataWatcher.getWatchableObjectByte(DW_ARMOR); if(armor != 0) { //if at least one bit of armor is present if(amount < getDamageThreshold()) return false; //chances of armor being broken off if(amount > 1 && isArmorBroken(amount)) { breakOffArmor(); amount *= 0.25F; } amount -= getDamageThreshold(); if(amount < 0) return true; } amount = this.calculateDamage(amount); } if(source.isFireDamage()) { amount *= 0.7F; } else if(source.getDamageType().equals("player")) { amount *= getScale() < 1.25 ? 1.5 : getScale() < 1.3 ? 0.8 : 0.5; } else if(source == ModDamageSource.acid || source.equals(new DamageSource(ModDamageSource.s_acid))){ amount = 0; } else if(source == DamageSource.inWall) { amount *= 15F; } if(this.isPotionActive(HbmPotion.phosphorus.getId())){ amount *= 1.5F; } boolean alive = this.getHealth() > 0; boolean wasAttacked = super.attackEntityFrom(source, amount); if(alive && this.getHealth() <= 0) { if(doesInfectedSpawnMaggots() && this.dataWatcher.getWatchableObjectByte(DW_SUBTYPE) == TYPE_INFECTED) { int j = 2 + this.rand.nextInt(3); for(int k = 0; k < j; ++k) { float f = ((float) (k % 2) - 0.5F) * 0.5F; float f1 = ((float) (k / 2) - 0.5F) * 0.5F; EntityParasiteMaggot maggot = new EntityParasiteMaggot(worldObj); maggot.setLocationAndAngles(this.posX + (double) f, this.posY + 0.5D, this.posZ + (double) f1, this.rand.nextFloat() * 360.0F, 0.0F); maggot.motionX = f; maggot.motionZ = f1; maggot.velocityChanged = true; this.worldObj.spawnEntityInWorld(maggot); } } } return wasAttacked; } public boolean doesInfectedSpawnMaggots() { return true; } public boolean isArmorBroken(float amount) { return this.rand.nextInt(100) <= Math.min(Math.pow(amount * 0.6, 2), 100); } public float calculateDamage(float amount) { byte armor = this.dataWatcher.getWatchableObjectByte(DW_ARMOR); int divisor = 1; for(int i = 0; i < 5; i++) { if((armor & (1 << i)) > 0) { divisor++; } } amount /= divisor; return amount; } public float getDamageThreshold() { return 0.5F; } public void breakOffArmor() { byte armor = this.dataWatcher.getWatchableObjectByte(DW_ARMOR); List indices = Arrays.asList(0, 1, 2, 3, 4); Collections.shuffle(indices); for(Integer i : indices) { byte bit = (byte) (1 << i); if((armor & bit) > 0) { armor &= ~bit; armor = (byte) (armor & 0b11111); this.dataWatcher.updateObject(DW_ARMOR, armor); worldObj.playSoundAtEntity(this, "mob.zombie.woodbreak", 1.0F, 1.25F); break; } } } @Override protected void updateArmSwingProgress() { int i = this.swingDuration(); if(this.isSwingInProgress) { ++this.swingProgressInt; if(this.swingProgressInt >= i) { this.swingProgressInt = 0; this.isSwingInProgress = false; } } else { this.swingProgressInt = 0; } this.swingProgress = (float) this.swingProgressInt / (float) i; } public int swingDuration() { return 15; } @Override public void setInWeb() { } @Override public boolean isOnLadder() { return this.isBesideClimbableBlock(); } public boolean isBesideClimbableBlock() { return (this.dataWatcher.getWatchableObjectByte(DW_WALL) & 1) != 0; } public void setBesideClimbableBlock(boolean climbable) { byte watchable = this.dataWatcher.getWatchableObjectByte(DW_WALL); if(climbable) { watchable = (byte) (watchable | 1); } else { watchable &= -2; } this.dataWatcher.updateObject(DW_WALL, Byte.valueOf(watchable)); } @Override public boolean attackEntityAsMob(Entity victim) { if(this.isSwingInProgress) return false; this.swingItem(); if(this.dataWatcher.getWatchableObjectByte(DW_SUBTYPE) == TYPE_INFECTED && victim instanceof EntityLivingBase) { ((EntityLivingBase) victim).addPotionEffect(new PotionEffect(Potion.poison.id, 100, 2)); ((EntityLivingBase) victim).addPotionEffect(new PotionEffect(Potion.confusion.id, 100, 0)); } return super.attackEntityAsMob(victim); } @Override public EnumCreatureAttribute getCreatureAttribute() { return EnumCreatureAttribute.ARTHROPOD; } /// TASK SYSTEM START /// public int getCurrentTask(){ return currentTask; } public EntityWaypoint getWaypoint(){ return taskWaypoint; } /** * Sets a new task for the glyphid to do, a waypoint alongside with that task, and refreshes their waypoint coordinates * @param task The task the glyphid is to do, refer to carryOutTask() * @param waypoint The waypoint for the task, can be null */ public void setCurrentTask(int task, @Nullable EntityWaypoint waypoint){ this.currentTask = task; this.taskWaypoint = waypoint; this.hasWaypoint = waypoint != null; if(taskWaypoint != null) { taskX = (int) taskWaypoint.posX; taskY = (int) taskWaypoint.posY; taskZ = (int) taskWaypoint.posZ; if(taskWaypoint.highPriority) { this.entityToAttack = null; this.setPathToEntity(null); } } carryOutTask(); } /** * Handles the task system, used mainly for things that only need to be done once, such as setting targets */ public void carryOutTask(){ int task = getCurrentTask(); switch(task){ case TASK_RETREAT_FOR_REINFORCEMENTS: if(taskWaypoint != null) { communicate(TASK_FOLLOW, taskWaypoint); setCurrentTask(TASK_FOLLOW, taskWaypoint); } break; case TASK_INITIATE_RETREAT: if(!worldObj.isRemote && taskWaypoint == null) { // Then, Come back later EntityWaypoint additional = new EntityWaypoint(worldObj); additional.setLocationAndAngles(posX, posY, posZ, 0, 0); // First, go home and get reinforcements EntityWaypoint home = new EntityWaypoint(worldObj); home.setWaypointType(TASK_RETREAT_FOR_REINFORCEMENTS); home.setAdditionalWaypoint(additional); home.setHighPriority(); home.setLocationAndAngles(homeX, homeY, homeZ, 0, 0); worldObj.spawnEntityInWorld(home); this.taskWaypoint = home; communicate(TASK_FOLLOW, home); setCurrentTask(TASK_FOLLOW, taskWaypoint); break; } break; case TASK_DIG: shouldDig = true; break; default: break; } } /** Copies tasks and waypoint to nearby glyphids. Does not work on glyphid scouts */ public void communicate(int task, @Nullable EntityWaypoint waypoint) { int radius = waypoint != null ? waypoint.radius : 4; AxisAlignedBB bb = AxisAlignedBB.getBoundingBox(this.posX, this.posY, this.posZ, this.posX, this.posY, this.posZ).expand(radius, radius, radius); List bugs = worldObj.getEntitiesWithinAABBExcludingEntity(this, bb); for(Entity e : bugs) { if(e instanceof EntityGlyphid && !(e instanceof EntityGlyphidScout)) { if(((EntityGlyphid) e).getCurrentTask() != task) { ((EntityGlyphid) e).setCurrentTask(task, waypoint); } } } } /** What each type of glyphid does when it is time to expand the hive. * @return Whether it has expanded successfully or not * **/ public boolean expandHive(){ return false; } public boolean isAtDestination() { int destinationRadius = taskWaypoint != null ? (int) Math.pow(taskWaypoint.radius, 2) : 25; return this.getDistanceSq(taskX, taskY, taskZ) <= destinationRadius; } ///TASK SYSTEM END ///DIGGING SYSTEM START /** Handles the special digging system, used in Rampant mode due to high potential for destroyed bases**/ public MovingObjectPosition findWaypointObstruction(){ Vec3 bugVec = Vec3.createVectorHelper(posX, posY + getEyeHeight(), posZ); Vec3 waypointVec = Vec3.createVectorHelper(taskX, taskY, taskZ); //incomplete forge docs my beloved MovingObjectPosition obstruction = worldObj.func_147447_a(bugVec, waypointVec, false, true, false); if(obstruction != null){ Block blockHit = worldObj.getBlock(obstruction.blockX, obstruction.blockY, obstruction.blockZ); if(blockHit.getExplosionResistance(null) <= blastResToDig){ return obstruction; } } return null; } public void digToWaypoint(MovingObjectPosition obstacle){ EntityWaypoint target = new EntityWaypoint(worldObj); target.setLocationAndAngles(obstacle.blockX, obstacle.blockY, obstacle.blockZ, 0 , 0); target.radius = 5; worldObj.spawnEntityInWorld(target); previousTask = getCurrentTask(); previousWaypoint = getWaypoint(); setCurrentTask(TASK_DIG, target); Vec3 vec = Vec3.createVectorHelper(posX, posY, posZ); int maxDist = (int) (Math.sqrt(vec.squareDistanceTo(taskX, taskY, taskZ)) * 1.2); this.setPathToEntity(PathFinderUtils.getPathEntityToCoordPartial(worldObj, this, taskX, taskY, taskZ, maxDist, true, false, true, true)); communicate(TASK_DIG, target); } ///DIGGING END @Override public void writeEntityToNBT(NBTTagCompound nbt) { super.writeEntityToNBT(nbt); nbt.setByte("armor", this.dataWatcher.getWatchableObjectByte(DW_ARMOR)); nbt.setByte("subtype", this.dataWatcher.getWatchableObjectByte(DW_SUBTYPE)); nbt.setBoolean("hasHome", hasHome); nbt.setInteger("homeX", homeX); nbt.setInteger("homeY", homeY); nbt.setInteger("homeZ", homeZ); nbt.setBoolean("hasWaypoint", hasWaypoint); nbt.setInteger("taskX", taskX); nbt.setInteger("taskY", taskY); nbt.setInteger("taskZ", taskZ); nbt.setInteger("task", currentTask); } @Override public void readEntityFromNBT(NBTTagCompound nbt) { super.readEntityFromNBT(nbt); this.dataWatcher.updateObject(DW_ARMOR, nbt.getByte("armor")); this.dataWatcher.updateObject(DW_SUBTYPE, nbt.getByte("subtype")); this.hasHome = nbt.getBoolean("hasHome"); this.homeX = nbt.getInteger("homeX"); this.homeY = nbt.getInteger("homeY"); this.homeZ = nbt.getInteger("homeZ"); this.hasWaypoint = nbt.getBoolean("hasWaypoint"); this.taskX = nbt.getInteger("taskX"); this.taskY = nbt.getInteger("taskY"); this.taskZ = nbt.getInteger("taskZ"); this.currentTask = nbt.getInteger("task"); } @Override public boolean getCanSpawnHere() { return this.worldObj.difficultySetting != EnumDifficulty.PEACEFUL && this.worldObj.checkNoEntityCollision(this.boundingBox) && this.worldObj.getCollidingBoundingBoxes(this, this.boundingBox).isEmpty() && !this.worldObj.isAnyLiquid(this.boundingBox); } }