Minecraft Modding: Custom Entity AI

Introduction


In any computer game, the non-player characters (called "entities" in Minecraft) act in ways that are sort of smart -- they have artificial intelligence (AI). AI consists of the entity making a decision then acting on the decision. The decisions though should not happen too frequently, otherwise they would be too good and would beat the players too easily. So instead the decisions are made occasionally and with some random occurrence. For example a EntityCow will stand in one place for a while then occasionally wander around. The AI in Minecraft is mostly related to controlling the movement and the targeting, but theoretically you can add any other type of activity an entity might do.

History Of Minecraft Entity AI


In earlier versions of Minecraft, the AI was simply coded directly in the update functions, including an updateAITick() method and an updateEntityActionState() method. By coding each individually, they probably had to do a lot of cut and paste and ran the risk of inconsistencies in behavior so they came up with a more organized approach to applying AI. So 1.7.x they introduced the "new" AI system.

However, they didn't apply it to all mobs initially. So they made a method called isAIEnabled() which returns true when the entity is supposed to use the new system. However, this doesn't mean that entities that return false have no AI, it just means they use the old style.

In 1.8 they finally converted all the entities to use the new AI system.

This progression is important to understand if you are maintaining a mod across several versions. For example, if you wanted to add a new AI task to all vanilla mobs, you'd find that in 1.7.10 things like EntitySpider would not process the AI. So you would have to copy the AI code into the update methods. On the other hand, in 1.8 you could simply add the AI task.

This tutorial is about the new AI system.

The New AI Architecture


To use the built-in AI processing, you need to use EntityCreature or an extended class. This gives two built in task lists of class type EntityAITasks. There is one task list for movement-related AI (called tasks) and another task list for the targeting AI (called targetTasks), since it is okay for both types to be operating concurrently. Theoretically you could add additional EntityAITasks if you had a set of AI that could be operated concurrently with movement and targeting, but in most cases you can use the existing lists and just avoid any masking.

It is important that different AI classes don't conflict with one another, so Minecraft came up with a way of "masking" the tasks so that similar types of AI can't operate at the same time. This is controlled with the setMutexBits() method explained below.

In addition to masking, there is also a priority order in which the AI is tested on whether it should start executing. This is set when you add the task to the task list.  For your custom entity, it is usually a good idea to build up your own AI task list, starting with clearing the one from the superclass. That list could just be a combination of existing AI or can include some custom AI.
This tutorial will try to explain all the above, using my EntityElephant as an example.

If you want to see my elephant rear up (using the AI described below) you can see it near the end of the video at: Video of Jabelar's Custom Entities With AI And Animations

Multiple AI Task Lists

As mentioned above, there are actually two built-in task lists: tasks and targetTasks.

These two lists are independent and concurrent. In other words, there can be a task on each list running at the same time, and the priority and mutex bits (explained below) are separate for each list.

As indicated by the task list names, targetTasks is intended for finding new attack targets, and tasks is intended for all other activities such as wandering, actually attacking (based on the target found in the targetTasks AI), begging, etc.

How The AI Executes


Overall the sequence that AI is executed is something like this (on each list separately):
  1. Minecraft starts at the priority 0 task in the list and checks if it is masked by any already running AI.
  2. If it is not masked, the shouldExecute() method is checked to see if it returns true.
  3. If shouldExecute() returns true, then the startExecute() method will be called and this method will initiate the entity according to what it should do for that AI.
  4. Minecraft continues up the AI list checking steps #2 and #3 above for lower and lower priority (higher priority number) until it gets to the end of the list. 
  5. Then every active AI has the continueExecuting() method checked, and if true that method can update anything according to that AI and if false the method should clean up the entity state to finish that AI.
  6. In other words, for your AI to execute it needs to not be masked and needs the shouldExecute() method to return true.

Ensure That The New AI Is Enabled


In your custom entity class, you should make sure that the isAIEnabled() method is overridden such that it returns true.  For example:

    @Override
    protected boolean isAIEnabled()
    {
       return true;
    }
    


    Create Custom AI Task Classes


    The class should extend EntityAIBase.

      AI Task Class Constructor


      The constructor should usually take the custom entity as a parameter.  This allows you to both react and change fields from the entity instance.

        Masking Tasks Using setMutexBits()

        In the constructor it is very important to set the mutexBits to prevent AI from conflicting.  Basically the way this works is that if two AI classes have mutexBits that have any bits that overlap then they cannot operate at the same time.  This is important because you don't want the entity to decide to wander when it is in the middle of panicking, and so forth.

        The mutexBit should be set according to the following guidelines:
        • If you are extending or copying a vanilla AI, you'll typically be okay using same mutexBits setting but consider the following:
        • If you want to make it compatible with swimming, begging and watching closest then you should set mutexBits to 1.
        • If you want to make it compatible with swimming, but not begging and watching closest you should set mutexBits to 3.
        • If you want to make it compatible with begging and watching closest, but incompatible with swimming you should set mutexBits to 5.
        • If you want to make a new AI that is incompatible with everything vanilla, then you should set the mutexBits to 7.
        • If you want to make a new AI that is compatible with everything vanilla, then you can choose to set mutexBits to 8 (or any larger power of 2).
          For example, the constructor for one of my animal entity AI:

          private final EntityHerdAnimal theEntity;
          
          public EntityAIPanicHerdAnimal(EntityHerdAnimal par1Entity)
          {
             theEntity = par1Entity;
             setMutexBits(1);
          
              // DEBUG
              System.out.println("EntityAIPanicHerdAnimal constructor()");
          }
          


          AI Task shouldExecute() Method


          The shouldExecute() method will be tested every tick that the AI isn't masked.  If it returns true then the AI will begin and startExecute() will be called.

          The shouldExecute() method should have whatever code makes sense for your entity.  For example, if you want your entity to run away from creepers that get too close, then you would check for any creepers nearby and return true if it finds one.

          For example, I wanted my elephant entity to rear up when attacked instead of running away.  I actually set the isRearingFirstTick() in my entity class and then check it here.:

            @Override
            public boolean shouldExecute()
            {
                if (theEntity.getAITarget() == null && theEntity.isBurning())
                {
                    return false;
                }
                else
                {
                    if (theEntity.isRearingFirstTick()) // only set the first tick that is rearing
                    {
                        return true;                    
                    }
                    else
                    {
                        return false;
                    }
                }       
            }
            


            AI Task startExecuting() Method


            The startExecuting() method is called the first time shouldExecute() returns true.  It can be used to set up some stuff if needed.  I personally organize my code so that the continueExecuting() method does more of the work.  So I all have is:

            @Override
            public void startExecuting()
            {
               // DEBUG
               System.out.println("AIPanic startExecute()");
            }
            


            AI Task continueExecuting() Method


            The continueExecuting() method is called every tick and used to test whether the AI should continue.  You can also use this method to do whatever further processing you want as appropriate to the continuing AI.

            In my example, I was having an elephant entity rear up.  Since rearing would take some time, I had a counter (in the entity class) that would count down to determine whether it should be finished. So all I had to do was to test whether that counter had counted down.

            If you find that the AI should finish, you can clean up the values of the fields in your entity.  For example here I turn off the isRearing() and then set the elephant to attack back.

            @Override
            public boolean continueExecuting()
            {
               theEntity.decrementRearingCounter();;
               Boolean continueExecuting = theEntity.getRearingCounter()>0; 
               if (!continueExecuting)
               {
                  theEntity.setRearing(false);
                  // now attack back
                  theEntity.setAttackTarget(theEntity.getLastAttacker()); 
               }
               // DEBUG
               System.out.println("EntityAIPanicHerdAnimal continueExecuting ="
                  +continueExecuting);
               return (continueExecuting);
            }
            


            Setup Your Entity AI Task List


            In your custom entity class' constructor you need to set up the task lists.  So I create a method called setupAI() and then call that from the constructor.  Since it is just a list, it is a good idea to clear the list since the EntityCreature superclass has already added some tasks.

            I consider the navigator setting to be related to AI, so I set it here as well. 

            You should think about the priority numbers assigned.  If an two tasks mask each other, then the higher priority one will have a chance to more often (since it will mask the lower priority one).  To put it another way, a low priority task (one with higher number) will only happen if all the lower priority masking AI are not executing.

            Anyway, here is the example of how I created the task list for my elephant entity.  Note that I combined the built-in AI with two custom AI (EntityAIPanicHerdAnimal and EntityAIHurtByTargetHerdAnimal).  Since my elephant was a mostly passive animal that only attacked when it was attacked, I didn't have it actively target anything except when it was hurt.  In contrast, I have created a tiger entity that actively hunted pigs, in which case there was an additional targeting task.  For tamable entities you might also add a task where it targets anything that hurts the owner, and another task where it targets anything the owner targets.  I think you get the idea..

            // set up AI tasks
            protected void setupAI()
            {
               getNavigator().setAvoidsWater(true);
               clearAITasks(); // clear any tasks assigned in super classes
               tasks.addTask(0, new EntityAISwimming(this));
               tasks.addTask(1, new EntityAIPanicHerdAnimal(this));
               // the leap and the collide together form an actual attack
               tasks.addTask(2, new EntityAILeapAtTarget(this, 0.4F));
               tasks.addTask(3, new EntityAIAttackOnCollide(this, 1.0D, true));
               tasks.addTask(5, new EntityAIMate(this, 1.0D));
               tasks.addTask(6, new EntityAITempt(this, 1.25D, Items.wheat, false));
               tasks.addTask(7, new EntityAIFollowParent(this, 1.25D));
               tasks.addTask(8, new EntityAIWander(this, 1.0D));
               tasks.addTask(9, new EntityAIWatchClosest(this, EntityPlayer.class, 6.0F));
               tasks.addTask(10, new EntityAILookIdle(this));
               targetTasks.addTask(0, new EntityAIHurtByTargetHerdAnimal(this, true));      
            }
            
            protected void clearAITasks()
            {
               tasks.taskEntries.clear();
               targetTasks.taskEntries.clear();
            }
            


            More On The Task Priority Numbers


            It is important to note that the numbers assigned during the addTask()are NOT indexes.  This means that:
            • You cannot replace a task by simply adding another task with the same number.
            • The numbers do not have to be unique, or consecutive.  You can inspect how this works by looking at the source for the canUse() method in the EntityAITasks class.  Basically it iterates through the list and compares the priority of the currently executing task to the next one in the list and the first one it finds with higher priority is executed to completion.  So if two have same priority, then the one that was added to the list first will have actual priority.
            • You can't use the number to look up the task, instead if you need to do this (maybe to remove a task later) you can create a field for each instance so I can reference it later if I need.  For example, you would create fields like the following, and then reference these during the addTask() method calls:
            // good to have instances of AI so task list can be modified, including in sub-classes
            protected EntityAIBase aiSwimming = new EntityAISwimming(this);
            protected EntityAIBase aiLeapAtTarget = new EntityAILeapAtTarget(this, 0.4F);
            protected EntityAIBase aiAttackOnCollide = new EntityAIAttackOnCollide(this, 
                  1.0D, true);
            protected EntityAIBase aiFollowOwner = new EntityAIFollowOwner(this, 1.0D, 10.0F, 
                  2.0F);
            protected EntityAIBase aiMate = new EntityAIMate(this, 1.0D);
            protected EntityAIBase aiWander = new EntityAIWander(this, 1.0D);
            protected EntityAIBase aiBeg = new EntityAIBegBigCat(this, 8.0F); 
            protected EntityAIBase aiWatchClosest = new EntityAIWatchClosest(this, 
                  EntityPlayer.class, 8.0F);
            protected EntityAIBase aiLookIdle = new EntityAILookIdle(this);
            protected EntityAIBase aiOwnerHurtByTarget = new EntityAIOwnerHurtByTarget(this);
            protected EntityAIBase aiOwnerHurtTarget = new EntityAIOwnerHurtTarget(this);
            protected EntityAIBase aiHurtByTarget = new EntityAIHurtByTarget(this, true);
            protected EntityAIBase aiTargetNonTamedSheep = new EntityAITargetNonTamed(this, 
                  EntitySheep.class, 200, false);
            protected EntityAIBase aiTargetNonTamedCow = new EntityAITargetNonTamed(this, 
                  EntityCow.class, 200, false);
            protected EntityAIBase aiTargetNonTamedPig = new EntityAITargetNonTamed(this, 
                  EntityPig.class, 200, false);
            protected EntityAIBase aiTargetNonTamedChicken = new EntityAITargetNonTamed(this, 
                  EntityChicken.class, 200, false);
            protected EntityAIBase aiTargetNonTamedHerdAnimal = new EntityAITargetNonTamed(this, 
                  EntityHerdAnimal.class, 200, false);
            


            Conclusion


            I hope this helps your understanding. Please feel free to suggest corrections and improvements.

              Appendix: The MutexBits Settings For The Built-In AI Classes


              To better understand what tasks are masked by the MutexBits, here is a listing of the settings for each built-in AI task.

                Category 1 (seems to be most "targeted movement" type AI)

                • AvoidEntity
                • CreeperSwell
                • DefendVillage
                • FleeSun
                • HurtByTarget
                • MoveIndoors
                • MoveThroughVillage
                • MoveTowardsRestriction
                • MoveTowardsTarget
                • NearestAttackableTarget
                • OwnerHurtByTarget
                • OwnerHurtTarget
                • Panic
                • Play
                • RunAroundLikeCrazy
                • TargetNonTamed
                • Wander

                Category 2 (seems to be fairly "passive" type AI)

                • Beg
                • WatchClosest
                • LookAtTradePlayer

                Category 3 (seems to be "interactive" AI):

                • ArrowAttack
                • AttackOnCollide
                • FollowGollum
                • FollowOwner
                • LookAtVillager
                • LookIdle
                • Mate
                • OcelotAttack
                • Tempt
                • VillagerMate
                • WatchClosest2

                Category4 (swimming is category on its own, and is compatible with a lot of stuff)

                • Swimming

                Category 5

                • LeapAtTarget
                • OcelotSit
                • Sit
                • TradePlayer

                Category 7 (seems like non-AI, like being controlled by the player)

                • ControlledByPlayer
                • EatGrass

                Uncategorized (I'm not actually sure how these are handled, but I assume they are compatible with everything):

                • BreakDoor
                • DoorInteract
                • FollowParent
                • OpenDoor
                • RestrictOpenDoor
                • RestrictSun

                11 comments:

                1. Thanks for this! As somebody who is just starting to get into creating Minecraft mods, this is super helpful. Do you happen to have any source code on github or somewhere so that I can see how you went about creating your custom tasks?

                  ReplyDelete
                  Replies
                  1. Thanks for the feedback. I have a lot of code of course, but I purposefully don't share it because I feel that the only way to learn is to do it yourself. That is the way I learned it (there wasn't any tutorial for me to follow!). I see so many people copy code without understanding it that I've come to the conclusion that beginning programmers need to code every line themselves. However, maybe I'll take time to write a supplement about custom AI sometime.

                    Delete
                2. Hi Julian. Just an extra bit of information for your readers... perhaps obvious but I didn't get it the first time round...
                  the vanilla uses two task lists which execute independently.
                  The 'targetTasks' list has AI which looks for targets; other AI executes in the 'tasks' list.
                  When a target AI finds a target, it sets the parent entity target using EntityLiving.setAttackTarget. This is then noticed and acted on by one of the AI executing in the 'tasks' list.

                  The AI priority and mutex masks are separate for the two task lists.

                  ReplyDelete
                  Replies
                  1. Thanks for the suggestion. I added a section to emphasize that there are two lists running concurrently and independently, and also edited some other text to mention that there are two lists.

                    Delete
                3. does setting the mutex to 0 make it compatible with everything since 0 has no bits set?

                  ReplyDelete
                4. I'd like to say thanks for this tutorial, but I've installed Windows 10 and am having difficulty finding any of the AI files. What directory should I follow?

                  ReplyDelete
                  Replies
                  1. This comment has been removed by the author.

                    Delete
                  2. Follow a tutorial for setting up a Forge development environment (decomp Workspace). Then look in *Forge-Path*\build\tmp\recompSrc\net\minecraft\entity\ai

                    Delete
                5. The mutex list is priceless, thank you. It has enduring value, so please update it sometimes if you can.

                  For the methods to override, it may be a matter of "style", but it is probably better coding practice/discipline for the boolean methods to remain as pure decision makers without side-effects. There are further methods such as updateTask() and resetTask() that can handle the actions of continuing or quitting. I know it's a small thing, but we may have some budding young programmers among us who will seek careers in the art of software design, so we should strive to be teachers of good habits.

                  Note: As of mc 1.8, isAIEnabled() no longer exists (because of that history).

                  Now to go finish my "Lawnmower Goat", a passive sheep-like animal that actively seeks bushes, tall-grass and leaves within reach, eating them and creating a neatly manicured area.

                  ReplyDelete
                6. Hi I noticed a few things on owner and tamed. How do you mark a mob as owned by a player?

                  ReplyDelete