Jump to content

Recommended Posts

Posted

Hey guys im trying to create a custom entity with some custom AI attached, where the player can set a bunch of inventories as "extract" or "insert" and the entity will then move an air block next to the inventory and once within ~3 blocks range it will start transferring items.

 

most things work fine, but the 2 AI's ive added both return true leaving the entity to juggle between extracting and inserting items. while extracting should have the priority over inserting, while registering the tasks ive set the extract priority to 1 and the insert priority to 2 (swimming is at 0).

 

extract should return false once the entities inventory is full or there are no items in the connected inventories.

insert should return true while the entity has items in its inventory and there is a slot available in one of the connected insert inventories.

 

full project here: https://github.com/theredghostwolf/SteampunkRevolution

 

ive attached an image with what it looks like in game (red box is extract blue box is insert), what happens is it takes ~5 to 10 items then inserts those and grabs another 5 to 10 items and keeps stuttering between the 2 and i feel like i just messed up somewhere big time but im having a hard time pin pointing where it is.

 

this is the AI for extracting items

public class EntityAIRobotExtractItem extends EntityAIRobotBase {
	
	public EntityAIRobotExtractItem (EntityRobot robot, World world) {
		super(robot, world);
	}
	
	@Override
	public boolean shouldExecute() {
		getExtractInventories();
		for (int i = 0; i < this.targetList.size(); i++) {
			IItemHandler inv = this.targetList.get(i).inv;
			for (int j = 0; j < inv.getSlots(); j++) {
				ItemStack item = inv.getStackInSlot(j);
				if (invHelper.InventoryHasRoomForItem(this.robot.getItemStackHandler(), item)) {
					return true;
				}
			}
		}
		
		return false;
	}
	
	@Override
	public void updateTask () {
		if (this.target != null) {
		if (this.robot.getDistance(this.target.pos.getX(), this.target.pos.getY(), this.target.pos.getZ()) <= this.robot.interactRange) {
			for (int i = 0; i < this.target.inv.getSlots(); i++) {
				boolean transferedItem = false;
				ItemStack stack = this.target.inv.getStackInSlot(i);
				if (! stack.isEmpty()) {
					ItemStack extracted = this.target.inv.extractItem(i, this.robot.getItemTransferSpeed(), true);
					for (int j = 0; j < this.robot.getInventory().getSlots(); j++) {
						ItemStack remainder = this.robot.getInventory().insertItem(j, extracted, true);
						if (remainder.isEmpty()) {
							ItemStack extracted1 = this.target.inv.extractItem(i, this.robot.getItemTransferSpeed(), false);
							ItemStack remainder1 = this.robot.getInventory().insertItem(j, extracted1,false );
							transferedItem = true;
							break;
						} else if (remainder.getCount() < extracted.getCount()) {
							ItemStack extracted1 = this.target.inv.extractItem(i, remainder.getCount(), false);
							ItemStack remainder1 = this.robot.getInventory().insertItem(j, extracted1,false );
							transferedItem = true;
							break;
						}
						
						
					}
					if (transferedItem) {
						break;
					}
				}
			}
			
		} else {
			
				BlockPos p = findAirBlockNearTarget();
				this.robot.getNavigator().tryMoveToXYZ(p.getX(),p.getY(),p.getZ(), 1F);
			
			}
		}
	}
	
	@Override
	 public void startExecuting() {
		findExtractTarget();
		
	}
	 
	@Override
	 public void resetTask()  {
	     this.target = null;
	     this.targetList = new ArrayList<Target>();
	 }
	 
	@Override
	 public boolean shouldContinueExecuting() {
		getExtractInventories();
		findExtractTarget();
		if (this.target != null) {
			 TileEntity e = world.getTileEntity(target.pos);
			 if (e != null) {
				 if (e.hasCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY, this.target.facing)) {
					 SteampunkRevolutionMod.logger.log(Level.INFO, "extract = true");
					 return true;
				 } else {
					return false;
				 }
			 } else {
				 return false;
			 }
		} else {
			return false;
		}
	
	 }
	
	
}

 

insert AI

 

public class EntityAIRobotInsertItem extends EntityAIRobotBase {
	
	public EntityAIRobotInsertItem (EntityRobot robot, World world) {
		super(robot, world);
	}
	
	@Override
	public boolean shouldExecute() {
		getInsertInventories();
		IItemHandler robotInv = this.robot.getInventory();
		if (invHelper.inventoryHasItem(robotInv)) {
			for (int i = 0; i < this.targetList.size(); i++) {
				IItemHandler inv = this.targetList.get(i).inv;
				for (int j = 0; j < robotInv.getSlots(); j++) {
					if (! robotInv.getStackInSlot(i).isEmpty()) {
						if (invHelper.InventoryHasRoomForItem(inv,robotInv.getStackInSlot(i) )) {
							return true;
						}
					}
				}
			}
		}
		
		return false;
	}
	
	public void updateTask () {
		if (this.target != null) {
			if (this.robot.getDistance(this.target.pos.getX(), this.target.pos.getY(), this.target.pos.getZ()) <= this.robot.interactRange) {
				IItemHandler robotInv = this.robot.getInventory();
				boolean transferedItem = false;
				for (int i = 0; i < robotInv.getSlots(); i++) {
					if (! robotInv.getStackInSlot(i).isEmpty()) {
						ItemStack extracted = robotInv.extractItem(i, this.robot.getItemTransferSpeed(), true);
						for (int j = 0; j < this.target.inv.getSlots(); j++) {
							ItemStack remainder = this.target.inv.insertItem(j, extracted, true);
							if (remainder.isEmpty()) {
								ItemStack extracted1 = robotInv.extractItem(i, this.robot.getItemTransferSpeed(), false);
								ItemStack remainder1 = this.target.inv.insertItem(j, extracted1, false);
								transferedItem = true;
								break;
							} else if (remainder.getCount() < extracted.getCount()) {
								ItemStack extracted1 =robotInv.extractItem(i, remainder.getCount(), false);
								ItemStack remainder1 = this.target.inv.insertItem(j, extracted1,false );
								transferedItem = true;
								break;
							}
							
						}
						if (transferedItem) {
							break;
						}
					}
				}
				
			} else {
				BlockPos p = findAirBlockNearTarget();
				this.robot.getNavigator().tryMoveToXYZ(p.getX(),p.getY(),p.getZ(), 1F);
				}
			}
		
	}
	
	@Override
	 public void startExecuting() {
		findInsertTarget();
		
	}
	
	@Override
	 public void resetTask()  {
	     this.target = null;
	     this.targetList = new ArrayList<Target>();
	 }
	
	@Override
	 public boolean shouldContinueExecuting() {
		getInsertInventories();
		findInsertTarget();
		if (this.target != null) {
			
			 TileEntity e = world.getTileEntity(target.pos);
			 if (e != null) {
				 if (e.hasCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY, this.target.facing)) {
					 SteampunkRevolutionMod.logger.log(Level.INFO, "insert = true");
					 return true;
				 } else {
					return false;
				 }
			 } else {
				 return false;
			 }
		} else {
			return false;
		}
		 
		 

	 }

}

 

AI base

public class EntityAIRobotBase extends EntityAIBase {
	
	public InventoryHelper invHelper = new InventoryHelper();
	
	public EntityRobot robot;
	public World world;
	
	public List<Target> targetList;
	
	public Target target;
	
	public EntityAIRobotBase (EntityRobot robot, World world) {
		this.robot = robot;
		this.world = world;
	}

	@Override
	public boolean shouldExecute() {
		return false;
	}
	
	public void getExtractInventories () {
		this.targetList = new ArrayList<Target>();
		for (int i = 0; i < this.robot.ExtractPoints.size(); i++) {
			AccessPoint p = this.robot.ExtractPoints.get(i);
			TileEntity te = this.world.getTileEntity(p.pos);
			if (te != null && te.hasCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY, p.facing)) {
				IItemHandler inv = te.getCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY, p.facing);
				this.targetList.add(new Target(p.pos, te, inv, p.facing, this.robot.getDistance(p.pos.getX(), p.pos.getY(), p.pos.getZ())));
			}
		}
		Collections.sort(this.targetList, new Comparator<Target>() {
		    @Override
		    public int compare(Target lhs, Target rhs) {
		        // -1 - less than, 1 - greater than, 0 - equal, all inversed for descending
		        return lhs.distance < rhs.distance ? -1 : (lhs.distance > rhs.distance) ? 1 : 0;
		    }
		});
	}
	
	public void getInsertInventories () {
		this.targetList = new ArrayList<Target>();
		for (int i = 0; i < this.robot.InsertPoints.size(); i++) {
			AccessPoint p = this.robot.InsertPoints.get(i);
			TileEntity te = this.world.getTileEntity(p.pos);
			if (te != null && te.hasCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY, p.facing)) {
				IItemHandler inv = te.getCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY, p.facing);
				this.targetList.add(new Target(p.pos, te, inv, p.facing, this.robot.getDistance(p.pos.getX(), p.pos.getY(), p.pos.getZ())));
			}
		}
		Collections.sort(this.targetList, new Comparator<Target>() {
		    @Override
		    public int compare(Target lhs, Target rhs) {
		        // -1 - less than, 1 - greater than, 0 - equal, all inversed for descending
		        return lhs.distance < rhs.distance ? -1 : (lhs.distance > rhs.distance) ? 1 : 0;
		    }
		});
	}
	
	public class Target {
		
		BlockPos pos;
		TileEntity te;
		IItemHandler inv;
		EnumFacing facing;
		double distance;
		
		public Target (BlockPos pos, TileEntity te, IItemHandler inv, EnumFacing facing, double distance) {
			this.pos = pos;
			this.te = te;
			this.inv = inv;
			this.facing = facing;
			this.distance = distance;
		}
	}
	
	public void findExtractTarget () {
		this.target = null;
		boolean foundTarget = false;
		for (int i = 0; i < this.targetList.size(); i++) {
			IItemHandler inv = this.targetList.get(i).inv;
			for (int j = 0; j < inv.getSlots(); j++) {
				ItemStack item = inv.getStackInSlot(j);
				if (invHelper.InventoryHasRoomForItem(this.robot.getItemStackHandler(), item)) {
					this.target = this.targetList.get(i);
					foundTarget = true;
					break;
				}
			}
			if (foundTarget) {
				break;
			}
		}
	}
	
	public void findInsertTarget () {
		this.target = null;
		boolean foundTarget = false;
		IItemHandler robotInv = this.robot.getInventory();
		if (invHelper.inventoryHasItem(robotInv)) {
			for (int i = 0; i < this.targetList.size(); i++) {
				IItemHandler inv = this.targetList.get(i).inv;
				for (int j = 0; j < robotInv.getSlots(); j++) {
					if (! robotInv.getStackInSlot(i).isEmpty()) {
						if (invHelper.InventoryHasRoomForItem(inv,robotInv.getStackInSlot(i) )) {
							this.target = this.targetList.get(i);
							foundTarget = true;
							break;
						}
					}
				}
				if (foundTarget) {
					break;
				}
			}
		}
	}
	
	public BlockPos findAirBlockNearTarget () {
		if (this.target != null) {
			List<BlockPos> airblocks = new ArrayList<BlockPos>();
			if (this.world.isAirBlock(this.target.pos.north())) {
				airblocks.add(this.target.pos.north());
			} if (this.world.isAirBlock(this.target.pos.south())) {
				airblocks.add(this.target.pos.south());
			}  if (this.world.isAirBlock(this.target.pos.west())) {
				airblocks.add(this.target.pos.west());
			}  if (this.world.isAirBlock(this.target.pos.east())) {
				airblocks.add(this.target.pos.east());
			} 
			if (airblocks.isEmpty()) {
				return this.target.pos;
			} else {
				Random rand = new Random();
				return airblocks.get(rand.nextInt(airblocks.size()));
			}
			
		}
		return null;
	}

 

2017-12-20_16.03.38.png

Posted (edited)
1 hour ago, ghostwolf_ said:

extract should return false once the entities inventory is full or there are no items in the connected inventories.

insert should return true while the entity has items in its inventory and there is a slot available in one of the connected insert inventories.

 

So obviously when partially full there will be a conflict here, both will be true.

 

Regarding the AI, I don't think priority alone controls this -- it works together with the idea of "compatible" and "interruptible". Let's look at the code. First we have the onUpdateTasks() method:

 public void onUpdateTasks()
    {
        ArrayList arraylist = new ArrayList();
        Iterator iterator;
        EntityAITasks.EntityAITaskEntry entityaitaskentry;

        if (this.tickCount++ % this.tickRate == 0)
        {
            iterator = this.taskEntries.iterator();

            while (iterator.hasNext())
            {
                entityaitaskentry = (EntityAITasks.EntityAITaskEntry)iterator.next();
                boolean flag = this.executingTaskEntries.contains(entityaitaskentry);

                if (flag)
                {
                    if (this.canUse(entityaitaskentry) && this.canContinue(entityaitaskentry))
                    {
                        continue;
                    }

                    entityaitaskentry.action.resetTask();
                    this.executingTaskEntries.remove(entityaitaskentry);
                }

                if (this.canUse(entityaitaskentry) && entityaitaskentry.action.shouldExecute())
                {
                    arraylist.add(entityaitaskentry);
                    this.executingTaskEntries.add(entityaitaskentry);
                }
            }
        }
        else
        {
            iterator = this.executingTaskEntries.iterator();

            while (iterator.hasNext())
            {
                entityaitaskentry = (EntityAITasks.EntityAITaskEntry)iterator.next();

                if (!entityaitaskentry.action.continueExecuting())
                {
                    entityaitaskentry.action.resetTask();
                    iterator.remove();
                }
            }
        }

 

You can see that the iteration continues whether each task continues or starts, however there is a test for canUse(). Let's look at that more closely. Here is the code for canUse():

    /**
     * Determine if a specific AI Task can be executed, which means that all running higher (= lower int value) priority
     * tasks are compatible with it or all lower priority tasks can be interrupted.
     */
    private boolean canUse(EntityAITasks.EntityAITaskEntry p_75775_1_)
    {
        this.theProfiler.startSection("canUse");
        Iterator iterator = this.taskEntries.iterator();

        while (iterator.hasNext())
        {
            EntityAITasks.EntityAITaskEntry entityaitaskentry = (EntityAITasks.EntityAITaskEntry)iterator.next();

            if (entityaitaskentry != p_75775_1_)
            {
                if (p_75775_1_.priority >= entityaitaskentry.priority)
                {
                    if (this.executingTaskEntries.contains(entityaitaskentry) && !this.areTasksCompatible(p_75775_1_, entityaitaskentry))
                    {
                        this.theProfiler.endSection();
                        return false;
                    }
                }
                else if (this.executingTaskEntries.contains(entityaitaskentry) && !entityaitaskentry.action.isInterruptible())
                {
                    this.theProfiler.endSection();
                    return false;
                }
            }
        }

        this.theProfiler.endSection();
        return true;
    }

 

So you see the key is that depending on the priority relationship, the tasks are checked for whether they are compatible and/or iterruptible. So let's finally look at the areTasksCompatible() and isInterruptible() methods:

   /**
     * Returns whether two EntityAITaskEntries can be executed concurrently
     */
    private boolean areTasksCompatible(EntityAITasks.EntityAITaskEntry p_75777_1_, EntityAITasks.EntityAITaskEntry p_75777_2_)
    {
        return (p_75777_1_.action.getMutexBits() & p_75777_2_.action.getMutexBits()) == 0;
    }

    /**
     * Determine if this AI Task is interruptible by a higher (= lower value) priority task. All vanilla AITask have
     * this value set to true.
     */
    public boolean isInterruptible()
    {
        return true;
    }

 

To make a task so it cannot be interrupted, you need to override it and return false. That might be enough to fix your issue, although technically there may be cases you want it to be interruptible (like what if entity is attacked or something?)

 

For compatibility, they use mutex (mutually exclusive) bits. Basically this is a common computer science technique the essentially uses bit flags to indicate what can run at the same time. In Minecraft AI, this is generally used for categories of AI -- for example you wouldn't want both wandering and sitting to happen at the same time or the same thing you've been experiencing would happen. 

 

So the main point is that priority alone doesn't stop other AI from happening. You either have to adjust the mutex bits to make them mutually exclusive or you have to make the uninterruptible.

 

By the way I have a tutorial on all this here: http://jabelarminecraft.blogspot.com/p/minecraft-forge-1721710-custom-entity-ai.html

Edited by jabelar

Check out my tutorials here: http://jabelarminecraft.blogspot.com/

Posted
13 minutes ago, jabelar said:

To make a task so it cannot be interrupted, you need to override it and return false. That might be enough to fix your issue, although technically there may be cases you want it to be interruptible (like what if entity is attacked or something?)

 

For compatibility, they use mutex (mutually exclusive) bits. Basically this is a common computer science technique the essentially uses bit flags to indicate what can run at the same time. In Minecraft AI, this is generally used for categories of AI -- for example you wouldn't want both wandering and sitting to happen at the same time or the same thing you've been experiencing would happen. 

 

So the main point is that priority alone doesn't stop other AI from happening. You either have to adjust the mutex bits to make them mutually exclusive or you have to make the uninterruptible.

that clears up alot, i blindly assumed that the priority was the only thing.

 

i added the interruptible and set it to false, which makes it alot smoother and only 1 returns true at the time, but i still messed up somewhere as somehow the insert still has the priority, meaning it will only transfer a few items at the item instead of the 4 stacks it is capable of.

 

so ill have to do some debugging on that, i did glance over your tutorial earlier a bit but after reading the first bit i went on to try and get it to work rather then reading the later parts that included the info on this.

 

quick side question how do you properly set a home point for an entity, mainly upon being spawned because while testing they tend to wander off.

Posted (edited)
18 minutes ago, ghostwolf_ said:

quick side question how do you properly set a home point for an entity, mainly upon being spawned because while testing they tend to wander off

Honestly, to figure this sort of stuff out you should practice just looking at the vanilla code -- that's how I come up with my answers and tutorials. Just takes a couple minutes usually and will make you more independent and confident in your modding, especially since you are already comfortable with Java.

 

I took a quick look and it looks like EntityCreature is the level in the class type hierarchy where the homePosition and maximumHomeDistance fields are added. So if your entity extends EntityCreature (or subclass thereof) you already have that available. Otherwise, if you've extended EntityLiving or other higher class you'll have to recreate the functionality.

 

If you look at the call hierarchy for homePosition, it is used by some AI such as EntityAIMoveToBlock. So you might want to add that AI to your entity, or create a custom one that does something similar. Note to make sure you set the home position and home distance with the setHomePosAndDistance() method, as I don't think it is automatically set (it might be though, I didn't look too hard at the code).

 

In the end, a "home" position is just a block pos stored in your entity, so probably easy to make up your own mechanism. But check out how other entities use the homePosition and maximumHomeDistance fields for ideas.

Edited by jabelar

Check out my tutorials here: http://jabelarminecraft.blogspot.com/

Posted
Just now, jabelar said:

Honestly, to figure this sort of stuff out you should practice just looking at the vanilla code -- that's how I come up with my answers and tutorials. Just takes a couple minutes usually and will make you more independent and confident in your modding, especially since you are already comfortable with Java.

 

 

i looked at the home position from the creature file that how i knew it even existed and i wanted to use the functionality listed there.

i tend to look at a similar vanilla code or someone else their github to get an idea of how to approach things, or find a tutorial. but i couldnt really find anything on this

 

but since those fields are private i couldnt blindly set them.

 

setHomePosAndDistance

 

that function should do the trick,

 

but i wanted the home pos to be set upon spawning, but since the getPosition() returns 0,0,0 that wasnt really do able

 

so i made a second constructor which accepted a Blockpos and passed it to that function.

 

but i was hoping there was an easier way of doing it, since so far i use the summon command to spawn the entity. meaning that it wont set the home pos.

 

i plan on adding a function simelar to setting the insert and extract points for the home position, so i suppose that will have to do, just hoped that there was a way to set it upon being spawned, maybe via an Eventhandler or something might have to try that.

 

thanks for the advice.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Unfortunately, your content contains terms that we do not allow. Please edit your content to remove the highlighted words below.
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Announcements



×
×
  • Create New...

Important Information

By using this site, you agree to our Terms of Use.