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);
}
}