package crazypants.enderio.machine.obelisk.attractor; import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import net.minecraft.entity.EntityCreature; import net.minecraft.entity.EntityList; import net.minecraft.entity.EntityLiving; import net.minecraft.entity.EntityLivingBase; import net.minecraft.entity.ai.EntityAIBase; import net.minecraft.entity.ai.EntityAITasks; import net.minecraft.entity.ai.EntityAITasks.EntityAITaskEntry; import net.minecraft.entity.monster.EntityBlaze; import net.minecraft.entity.monster.EntityEnderman; import net.minecraft.entity.monster.EntityMob; import net.minecraft.entity.monster.EntityPigZombie; import net.minecraft.entity.monster.EntitySilverfish; import net.minecraft.entity.monster.EntitySlime; import net.minecraft.entity.monster.EntitySpider; import net.minecraft.item.ItemStack; import net.minecraft.pathfinding.PathEntity; import net.minecraft.server.MinecraftServer; import net.minecraft.tileentity.TileEntity; import net.minecraft.util.AxisAlignedBB; import net.minecraft.world.World; import net.minecraft.world.WorldSettings.GameType; import net.minecraftforge.common.util.FakePlayer; import com.enderio.core.client.render.BoundingBox; import com.enderio.core.common.util.BlockCoord; import com.enderio.core.common.vecmath.Vector3d; import com.mojang.authlib.GameProfile; import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.SideOnly; import crazypants.enderio.EnderIO; import crazypants.enderio.ModObject; import crazypants.enderio.config.Config; import crazypants.enderio.machine.AbstractPowerConsumerEntity; import crazypants.enderio.machine.FakePlayerEIO; import crazypants.enderio.machine.SlotDefinition; import crazypants.enderio.machine.ranged.IRanged; import crazypants.enderio.machine.ranged.RangeEntity; import crazypants.enderio.power.BasicCapacitor; public class TileAttractor extends AbstractPowerConsumerEntity implements IRanged { private AxisAlignedBB attractorBounds; private FakePlayer target; private int rangeSqu; private int range; private int powerPerTick; private Set<EntityLiving> tracking = new HashSet<EntityLiving>(); private int tickCounter = 0; private int maxMobsAttracted = 20; private boolean showingRange; public TileAttractor() { super(new SlotDefinition(12, 0)); } @Override public float getRange() { return range; } @Override @SideOnly(Side.CLIENT) public boolean isShowingRange() { return showingRange; } @SideOnly(Side.CLIENT) public void setShowRange(boolean showRange) { if(showingRange == showRange) { return; } showingRange = showRange; if(showingRange) { worldObj.spawnEntityInWorld(new RangeEntity(this)); } } @Override public World getWorld() { return worldObj; } @Override public void onCapacitorTypeChange() { switch (getCapacitorType()) { case ACTIVATED_CAPACITOR: range = Config.attractorRangeLevelTwo; powerPerTick = Config.attractorPowerPerTickLevelTwo; break; case ENDER_CAPACITOR: range = Config.attractorRangeLevelThree; powerPerTick = Config.attractorPowerPerTickLevelThree; break; case BASIC_CAPACITOR: default: range = Config.attractorRangeLevelOne; powerPerTick = Config.attractorPowerPerTickLevelOne; break; } rangeSqu = range * range; BoundingBox bb = new BoundingBox(new BlockCoord(this)); bb = bb.scale(range, range, range); attractorBounds = AxisAlignedBB.getBoundingBox(bb.minX, bb.minY, bb.minZ, bb.maxX, bb.maxY, bb.maxZ); setCapacitor(new BasicCapacitor(powerPerTick * 8, getCapacitor().getMaxEnergyStored(), powerPerTick)); } @Override public String getMachineName() { return ModObject.blockAttractor.unlocalisedName; } @Override protected boolean isMachineItemValidForSlot(int i, ItemStack itemstack) { if(!slotDefinition.isInputSlot(i)) { return false; } String mob = EnderIO.itemSoulVessel.getMobTypeFromStack(itemstack); if(mob == null) { return false; } Class<?> cl = (Class<?>) EntityList.stringToClassMapping.get(mob); if(cl == null) { return false; } return EntityLiving.class.isAssignableFrom(cl); } @Override public boolean isActive() { return hasPower(); } @Override protected boolean processTasks(boolean redstoneCheckPassed) { if(redstoneCheckPassed && hasPower()) { usePower(); } else { return false; } tickCounter++; if(tickCounter < 10) { for (EntityLiving ent : tracking) { onEntityTick(ent); } return false; } tickCounter = 0; Set<EntityLiving> trackingThisTick = new HashSet<EntityLiving>(); List<EntityLiving> entsInBounds = worldObj.getEntitiesWithinAABB(EntityLiving.class, attractorBounds); int candidates = 0; for (EntityLiving ent : entsInBounds) { if(!ent.isDead && isMobInFilter(ent)) { candidates++; if(tracking.contains(ent)) { trackingThisTick.add(ent); onEntityTick(ent); } else if(tracking.size() < maxMobsAttracted && trackMob(ent)) { trackingThisTick.add(ent); onTracked(ent); } } } for (EntityLiving e : tracking) { if (!trackingThisTick.contains(e)) { onUntracked(e); } } tracking.clear(); tracking = trackingThisTick; return false; } private void onUntracked(EntityLiving e) { if (e instanceof EntityEnderman) { e.getEntityData().setBoolean("EIO:tracked", false); } } private void onTracked(EntityLiving e) { if (e instanceof EntityEnderman) { e.getEntityData().setBoolean("EIO:tracked", true); } } @Override public void invalidate() { super.invalidate(); for (EntityLiving e : tracking) { onUntracked(e); } tracking.clear(); } protected double usePower() { return usePower(getPowerUsePerTick()); } protected int usePower(int wantToUse) { int used = Math.min(getEnergyStored(), wantToUse); setEnergyStored(Math.max(0, getEnergyStored() - used)); return used; } @Override public int getPowerUsePerTick() { return powerPerTick; } FakePlayer getTarget() { if(target == null) { target = new Target(); } return target; } public boolean canAttract(String entityId, EntityLiving mob) { return redstoneCheckPassed && hasPower() && isMobInFilter(entityId) && isMobInRange(mob); } private boolean isMobInRange(EntityLiving mob) { return isMobInRange(mob, rangeSqu); } private boolean isMobInRange(EntityLiving mob, int range) { if(mob == null) { return false; } return new Vector3d(mob.posX, mob.posY, mob.posZ).distanceSquared(new Vector3d(xCoord, yCoord, zCoord)) <= range; } private boolean isMobInFilter(EntityLiving ent) { return isMobInFilter(EntityList.getEntityString(ent)); } private boolean isMobInFilter(String entityId) { for (int i = slotDefinition.minInputSlot; i <= slotDefinition.maxInputSlot; i++) { if(inventory[i] != null) { String mob = EnderIO.itemSoulVessel.getMobTypeFromStack(inventory[i]); if(mob != null && mob.equals(entityId)) { return true; } } } return false; } private boolean trackMob(EntityLiving ent) { if(useSetTarget(ent)) { ((EntityMob) ent).setTarget(getTarget()); return true; } else if(useSpecialCase(ent)) { return applySpecialCase(ent); } else { return attractyUsingAITask(ent); } } private boolean attractyUsingAITask(EntityLiving ent) { tracking.add(ent); List<EntityAITaskEntry> entries = ent.tasks.taskEntries; boolean hasTask = false; EntityAIBase remove = null; boolean isTracked; for (EntityAITaskEntry entry : entries) { if(entry.action instanceof AttractTask) { AttractTask at = (AttractTask) entry.action; if(at.coord.equals(new BlockCoord(this)) || !at.continueExecuting()) { remove = entry.action; } else { return false; } } } if(remove != null) { ent.tasks.removeTask(remove); } cancelCurrentTasks(ent); ent.tasks.addTask(0, new AttractTask(ent, getTarget(), new BlockCoord(this))); return true; } private void cancelCurrentTasks(EntityLiving ent) { Iterator iterator = ent.tasks.taskEntries.iterator(); List<EntityAITasks.EntityAITaskEntry> currentTasks = new ArrayList<EntityAITasks.EntityAITaskEntry>(); while (iterator.hasNext()) { EntityAITaskEntry entityaitaskentry = (EntityAITasks.EntityAITaskEntry)iterator.next(); if(entityaitaskentry != null) { currentTasks.add(entityaitaskentry); } } //Only available way to stop current execution is to remove all current tasks, then re-add them for(EntityAITaskEntry task : currentTasks) { ent.tasks.removeTask(task.action); ent.tasks.addTask(task.priority, task.action); } } private boolean applySpecialCase(EntityLiving ent) { if(ent instanceof EntitySlime) { ent.faceEntity(getTarget(), 10.0F, 20.0F); return true; } else if(ent instanceof EntitySilverfish) { PathEntity pathentity = worldObj.getPathEntityToEntity(ent, getTarget(), getRange(), true, false, false, true); ((EntityCreature) ent).setPathToEntity(pathentity); return true; } else if(ent instanceof EntityBlaze) { return true; } return false; } private boolean useSpecialCase(EntityLiving ent) { return ent instanceof EntitySlime || ent instanceof EntitySilverfish || ent instanceof EntityBlaze; } private void onEntityTick(EntityLiving ent) { if(ent instanceof EntitySlime) { ent.faceEntity(getTarget(), 10.0F, 20.0F); } else if(ent instanceof EntitySilverfish) { if(tickCounter < 10) { return; } EntitySilverfish sf = (EntitySilverfish) ent; PathEntity pathentity = worldObj.getPathEntityToEntity(ent, getTarget(), getRange(), true, false, false, true); sf.setPathToEntity(pathentity); } else if(ent instanceof EntityBlaze) { EntityBlaze mob = (EntityBlaze) ent; double x = (xCoord + 0.5D - ent.posX); double y = (yCoord + 1D - ent.posY); double z = (zCoord + 0.5D - ent.posZ); double distance = Math.sqrt(x * x + y * y + z * z); if(distance > 1.25) { double speed = 0.01; ent.motionX += x / distance * speed; if(y > 0) { ent.motionY += (0.30000001192092896D - ent.motionY) * 0.30000001192092896D; } ent.motionZ += z / distance * speed; } } else if(ent instanceof EntityPigZombie || ent instanceof EntitySpider) { forceMove(ent); } else if(ent instanceof EntityEnderman) { ((EntityEnderman) ent).setTarget(getTarget()); } } private void forceMove(EntityLiving ent) { double x = (xCoord + 0.5D - ent.posX); double y = (yCoord + 1D - ent.posY); double z = (zCoord + 0.5D - ent.posZ); double distance = Math.sqrt(x * x + y * y + z * z); if(distance > 2) { EntityMob mod = (EntityMob) ent; mod.faceEntity(getTarget(), 180, 0); mod.moveEntityWithHeading(0, 1); if(mod.posY < yCoord) { mod.setJumping(true); } else { mod.setJumping(false); } } } private boolean useSetTarget(EntityLiving ent) { return ent instanceof EntityPigZombie || ent instanceof EntitySpider || ent instanceof EntitySilverfish; } private class Target extends FakePlayerEIO { public Target() { super(getWorldObj(), getLocation(), new GameProfile(null, ModObject.blockAttractor.unlocalisedName + ":" + getLocation())); posY += 1; } } private static class AttractTask extends EntityAIBase { private EntityLiving mob; private BlockCoord coord; private FakePlayer target; private String entityId; private int updatesSincePathing; private boolean started = false; private AttractTask(EntityLiving mob, FakePlayer target, BlockCoord coord) { this.mob = mob; this.coord = coord; this.target = target; entityId = EntityList.getEntityString(mob); } @Override public boolean shouldExecute() { return continueExecuting(); } @Override public void resetTask() { started = false; updatesSincePathing = 0; } @Override public boolean continueExecuting() { boolean res = false; TileEntity te = mob.worldObj.getTileEntity(coord.x, coord.y, coord.z); if(te instanceof TileAttractor) { TileAttractor attractor = (TileAttractor) te; res = attractor.canAttract(entityId, mob); } return res; } @Override public boolean isInterruptible() { return true; } @Override public void updateTask() { if(!started || updatesSincePathing > 20) { started = true; int speed = 1; mob.getNavigator().setAvoidsWater(false); boolean res = mob.getNavigator().tryMoveToEntityLiving(target, speed); if(!res) { mob.getNavigator().tryMoveToXYZ(target.posX, target.posY +1, target.posZ, speed); } updatesSincePathing = 0; } else { updatesSincePathing++; } } } }