package pneumaticCraft.common.ai; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import net.minecraft.entity.ai.EntityAIBase; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.util.Vec3; import net.minecraft.world.ChunkPosition; import net.minecraft.world.IBlockAccess; import net.minecraftforge.common.util.ForgeDirection; import pneumaticCraft.api.item.IPressurizable; import pneumaticCraft.common.item.ItemMachineUpgrade; import pneumaticCraft.common.item.ItemPneumaticArmor; import pneumaticCraft.common.item.Itemss; import pneumaticCraft.common.network.NetworkHandler; import pneumaticCraft.common.network.PacketSpawnParticle; import pneumaticCraft.common.progwidgets.IBlockOrdered; import pneumaticCraft.common.progwidgets.IBlockOrdered.EnumOrder; import pneumaticCraft.common.progwidgets.ProgWidgetAreaItemBase; import pneumaticCraft.common.progwidgets.ProgWidgetDigAndPlace; import pneumaticCraft.common.progwidgets.ProgWidgetPlace; import pneumaticCraft.common.util.PneumaticCraftUtils; import pneumaticCraft.common.util.ThreadedSorter; public abstract class DroneAIBlockInteraction<Widget extends ProgWidgetAreaItemBase> extends EntityAIBase{ protected final IDroneBase drone; protected final Widget widget; private final EnumOrder order; protected ChunkPosition curPos; private final List<ChunkPosition> area; protected final IBlockAccess worldCache; private final List<ChunkPosition> blacklist = new ArrayList<ChunkPosition>();//a list of position which weren't allowed to be digged in the past. private int curY; private int lastSuccessfulY; private int minY, maxY; private ThreadedSorter<ChunkPosition> sorter; private boolean aborted; protected boolean searching; //true while the drone is searching for a coordinate, false if traveling/processing a coordinate. private int searchIndex;//The current index in the area list the drone is searching at. private static final int LOOKUPS_PER_SEARCH_TICK = 30; //How many blocks does the drone access per AI update. private int totalActions; private int maxActions = -1; /** * * @param drone * @param speed * @param widget needs to implement IBlockOrdered */ public DroneAIBlockInteraction(IDroneBase drone, Widget widget){ this.drone = drone; setMutexBits(63);//binary 111111, so it won't run along with other AI tasks. this.widget = widget; order = widget instanceof IBlockOrdered ? ((IBlockOrdered)widget).getOrder() : EnumOrder.CLOSEST; area = widget.getCachedAreaList(); worldCache = ProgWidgetAreaItemBase.getCache(area, drone.getWorld()); if(area.size() > 0) { Iterator<ChunkPosition> iterator = area.iterator(); ChunkPosition pos = iterator.next(); minY = maxY = pos.chunkPosY; while(iterator.hasNext()) { pos = iterator.next(); minY = Math.min(minY, pos.chunkPosY); maxY = Math.max(maxY, pos.chunkPosY); } if(order == EnumOrder.HIGH_TO_LOW) { curY = maxY; } else if(order == EnumOrder.LOW_TO_HIGH) { curY = minY; } } } /** * Returns whether the EntityAIBase should begin execution. */ @Override public boolean shouldExecute(){ if(aborted || maxActions >= 0 && totalActions >= maxActions) { return false; } else { if(!searching) { searching = true; searchIndex = 0; curPos = null; lastSuccessfulY = curY; if(sorter == null || sorter.isDone()) sorter = new ThreadedSorter(area, new ChunkPositionSorter(drone)); return true; } else { return false; } } } private void updateY(){ searchIndex = 0; if(order == ProgWidgetPlace.EnumOrder.LOW_TO_HIGH) { if(++curY > maxY) curY = minY; } else if(order == ProgWidgetPlace.EnumOrder.HIGH_TO_LOW) { if(--curY < minY) curY = maxY; } } private boolean isYValid(int y){ return order == ProgWidgetPlace.EnumOrder.CLOSEST || y == curY; } public DroneAIBlockInteraction setMaxActions(int maxActions){ this.maxActions = maxActions; return this; } protected abstract boolean isValidPosition(ChunkPosition pos); protected abstract boolean doBlockInteraction(ChunkPosition pos, double distToBlock); /** * Returns whether an in-progress EntityAIBase should continue executing */ @Override public boolean continueExecuting(){ if(aborted) return false; if(searching) { if(!sorter.isDone()) return true;//Wait until the area is sorted from closest to furtherest. boolean firstRun = true; int searchedBlocks = 0; //keeps track of the looked up blocks, and stops searching when we reach our quota. while(curPos == null && curY != lastSuccessfulY && order != ProgWidgetDigAndPlace.EnumOrder.CLOSEST || firstRun) { firstRun = false; while(!shouldAbort() && searchIndex < area.size()) { ChunkPosition pos = area.get(searchIndex); if(isYValid(pos.chunkPosY) && !blacklist.contains(pos) && (!respectClaims() || !DroneClaimManager.getInstance(drone.getWorld()).isClaimed(pos))) { indicateToListeningPlayers(pos); if(isValidPosition(pos)) { curPos = pos; if(moveToPositions()) { if(moveIntoBlock()) { if(drone.getPathNavigator().moveToXYZ(curPos.chunkPosX, curPos.chunkPosY + 0.5, curPos.chunkPosZ)) { searching = false; totalActions++; if(respectClaims()) DroneClaimManager.getInstance(drone.getWorld()).claim(pos); blacklist.clear();//clear the list for next time (maybe the blocks/rights have changed by the time there will be dug again). return true; } } else { for(ForgeDirection dir : ForgeDirection.VALID_DIRECTIONS) { if(drone.getPathNavigator().moveToXYZ(curPos.chunkPosX + dir.offsetX, curPos.chunkPosY + dir.offsetY + 0.5, curPos.chunkPosZ + dir.offsetZ)) { searching = false; totalActions++; if(respectClaims()) DroneClaimManager.getInstance(drone.getWorld()).claim(pos); blacklist.clear();//clear the list for next time (maybe the blocks/rights have changed by the time there will be dug again). return true; } } } if(drone.getPathNavigator().isGoingToTeleport()) { searching = false; totalActions++; if(respectClaims()) DroneClaimManager.getInstance(drone.getWorld()).claim(pos); blacklist.clear();//clear the list for next time (maybe the blocks/rights have changed by the time there will be dug again). return true; } else { drone.addDebugEntry("gui.progWidget.general.debug.cantNavigate", pos); } } else { searching = false; totalActions++; return true; } } searchedBlocks++; } searchIndex++; if(searchedBlocks >= lookupsPerSearch()) return true; } if(curPos == null) updateY(); } if(!shouldAbort()) addEndingDebugEntry(); return false; } else { Vec3 dronePos = drone.getPosition(); double dist = curPos != null ? PneumaticCraftUtils.distBetween(curPos.chunkPosX + 0.5, curPos.chunkPosY + 0.5, curPos.chunkPosZ + 0.5, dronePos.xCoord, dronePos.yCoord, dronePos.zCoord) : 0; if(curPos != null) { if(!moveToPositions()) return doBlockInteraction(curPos, dist); if(respectClaims()) DroneClaimManager.getInstance(drone.getWorld()).claim(curPos); if(dist < (moveIntoBlock() ? 1 : 2)) { return doBlockInteraction(curPos, dist); } } return !drone.getPathNavigator().hasNoPath(); } } protected void addEndingDebugEntry(){ drone.addDebugEntry("gui.progWidget.blockInteraction.debug.noBlocksValid"); } protected int lookupsPerSearch(){ return LOOKUPS_PER_SEARCH_TICK; } protected boolean respectClaims(){ return false; } protected boolean moveIntoBlock(){ return false; } protected boolean shouldAbort(){ return aborted; } public void abort(){ aborted = true; } protected boolean moveToPositions(){ return true; } /** * Sends particle spawn packets to any close player that has a charged pneumatic helmet with entity tracker. * @param pos */ protected void indicateToListeningPlayers(ChunkPosition pos){ for(EntityPlayer player : (List<EntityPlayer>)drone.getWorld().playerEntities) { if(player.getCurrentArmor(3) != null && player.getCurrentArmor(3).getItem() == Itemss.pneumaticHelmet && ItemPneumaticArmor.getUpgrades(ItemMachineUpgrade.UPGRADE_ENTITY_TRACKER, player.getCurrentArmor(3)) > 0 && ((IPressurizable)Itemss.pneumaticHelmet).getPressure(player.getCurrentArmor(3)) > 0) { NetworkHandler.sendTo(new PacketSpawnParticle("reddust", pos.chunkPosX + 0.5, pos.chunkPosY + 0.5, pos.chunkPosZ + 0.5, 0, 0, 0), (EntityPlayerMP)player); } } } protected void addToBlacklist(ChunkPosition coord){ blacklist.add(coord); drone.sendWireframeToClient(coord.chunkPosX, coord.chunkPosY, coord.chunkPosZ); } }