ghostwolf_ Posted December 20, 2017 Posted December 20, 2017 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; } Quote
jabelar Posted December 20, 2017 Posted December 20, 2017 (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 December 20, 2017 by jabelar Quote Check out my tutorials here: http://jabelarminecraft.blogspot.com/
ghostwolf_ Posted December 20, 2017 Author Posted December 20, 2017 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. Quote
jabelar Posted December 20, 2017 Posted December 20, 2017 (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 December 20, 2017 by jabelar Quote Check out my tutorials here: http://jabelarminecraft.blogspot.com/
ghostwolf_ Posted December 20, 2017 Author Posted December 20, 2017 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. Quote
jabelar Posted December 20, 2017 Posted December 20, 2017 You may want to look at the entity join world event. I can't remember but the position may be set at that point. But yeah there is a potential for position not being set at the time of the constructor unless you explicitly set it. Quote Check out my tutorials here: http://jabelarminecraft.blogspot.com/
Recommended Posts
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.