• Our software update is now concluded. You will need to reset your password to log in. In order to do this, you will have to click "Log in" in the top right corner and then "Forgot your password?".
  • Welcome to PokéCommunity! Register now and join one of the best fan communities on the 'net to talk Pokémon and more! We are not affiliated with The Pokémon Company or Nintendo.

Research: Trainer AI Command Research

AkameTheBulbasaur

Akame Marukawa of Iyotono
409
Posts
10
Years
Acknowledgments
First and foremost I have to thank Knizz for her previous research on the Trainer AI Scripting system! Because of her hard work, I was able to have a foundation for all the research I've done. By extension, I also have to thank JPAN, because Knizz said she relied heavily on that as well.

If you want to see Knizz's original work, it is in this thread.

I should say that this particular research is for FireRed. I think that Emerald uses the same sort of system (with different offsets) but I haven't checked. Don't sue me if it's different!

And with that, let's get on with the show!

Introduction
There have been a few attempts in the past of improving the difficulty of ROM Hacks, such as DoesntKnowHowToPlay's Trainer EV hack (here), but I wasn't quite satisfied with that.

I used that hack, and JPAN's dynamic trainer level hack (to make trainer levels match your own), and that helped genuinely increase the difficulty, but I found it wasn't enough. I wanted to really control how the AI works from its core.

So I found Knizz's earlier thread from 2014, which detailed a sort of scripting system for the AI, theoretically making it possible to write your own scripts and customize the AI. It was a really good start, but I couldn't find very much information outside of that thread.

The last post in that thread is from 2015. The development on that front seemed to stagnate.

So I decided I would try to research the commands myself, just for fun, and see how far I got. I was originally intending to post what I found to that thread after I was done. Five months later and the post I'd written down was so long I thought it would be easier to give it it's own thread.

The Commands
There are two main variables in Trainer AI Scripting. The first and foremost is the Free Variable. This is like LASTRESULT in NPC scripting. Many of the commands store their results here, and you can then use this result for other commands.

Many commands jump to a given address if the free variable compares to something.

The second variable is the Considered Move Variable. This variable has the index number of the move being considered while the script is running. You can compare it to things or get Move data from it.

Useful numbers:
Attacker is 1, Defender is 0.
True is 1, False is 0.

Basic syntax rules:
The target (if applicable) always comes first, and the address to jump to (if applicable always comes last). If any of the below commands have more than two arguments, this is how the order will almost always go.

Some of the existing commands I thought were either written in a clunky way, or I thought of something I could add to them (I think one of them was bugged to begin with) so I rewrote them. I'll include the routines for the rewritten versions after the description of the commands.

I should note that there are, for some unknown reason, commands in default FireRed that do nothing. I don't mean they have a redundant or pointless effect, I mean they literally do nothing. I tried using one and it soft locked the game. This is useful because it means you can add about ten new commands without replacing or expanding the table!

In fact, because of the CallASM command (which I wrote recently and have included at the very bottom) you don't need to expand the table at all if you don't want to!

DISCLAIMER: Adding in the new or rewritten commands that will follow won't make the AI smarter. Only creating new scripts using the commands will.

WARNING ABOUT CERTAIN REWRITTEN COMMANDS!
The first iteration of this thread included a warning about how certain rewritten commands are incompatible with default AI scripts because the amount of arguments in them is different. Somehow, this warning was deleted, so I'm putting it back because it's kinda important lol.

JumpIfDamageBonusEquals has extra arguments, and thus if you put it in and do not change the default script, the game will crash.

This really shouldn't be an issue, as the modified command is really intended to be used with modified scripts. However, if for some reason you want to use the default scripts, follow the instructions below:

  1. Download (or make your own) this file. It is 16 MB of just FF.
  2. Open this in a THUMB editor.
    Spoiler:
  3. Go to the part that says ".equ Offset, 0x" and add after the 0x the offset of some free space. You will need 0xEF (239) bytes worth.
  4. Compile to a binary file with your favourite THUMB compiler.
  5. Create an ips patch by using the blank file as the unmodified file and your new binary file as the modified one.
  6. Now you can patch a FireRed ROM with it.

Documents
Since I keep updating this because I either add new commands or update old ones, I decided it would be way easier to have a Google Doc with all the information on it. It's easier for me to update that.

There are three. The first is the command description list. Please note that these descriptions are of the updated commands. So if a command was rewritten or updated, the rewritten/updated version is used. It also includes descriptions of new commands.

The second is a folder that has all the rewritten command routines in it.

The third is another folder that has all the new command routines in it.

Links
1. AI Command Descriptions
2. Rewritten Commands
3. New Commands


Callasm Command
I am putting this command separately because it is a big deal. With it, you can have as many commands as you need regardless of how big or small your command table is. You can literally use this to do pretty much anything you want.

The syntax looks like this

Callasm OFFSET + 1
Arguments for asm routine


Put the arguments for the command (if any) immediately after the pointer to the command routine. Then, just format the routine as if it were a normal command called from the table. The AI Cursor is updated in the routine it calls, so you can have as many or as little arguments as you need.

Spoiler:


Macros
The final gift I would like to impart on the masses at Pokecommunity is this. I've created a few macros to use to help compile scripts.

The link to view all of them is here.

The way to use them is to include the relevant macros at the top of the script (and by that I mean copy and paste them into the beginning of your script), and then just type the script using this sort of form:

.equ ROM, 0x8

Main:
JumpIfMoveScriptEquals 0x1 CauseSleep+ROM
ReturnToBattle

CauseSleep:
JumpIfStatus1Equals Defender 0x000000FF Decrease32+ROM
ReturnToBattle

Decrease32:
AddToViabilityScore 0xE0
ReturnToBattle

That way, you can just compile this with any sort of THUMB assembler. Remember to put the offset that you're inserting the script at after the ".equ ROM, 0x8". If you don't the pointers in your script will be all wrong.

I recommend

Conclusion
This thread is the culmination of over five months worth of research and work. My hope with all of this is to give ROM hackers the ability to truly customize the AI. This can be used not just to raise the difficulty/challenge of games, but also to add support to the ever-growing list of new moves and abilities being added to the gen III games.
 
Last edited:
239
Posts
8
Years
  • Age 31
  • Seen Apr 15, 2024
Awesome research! I had been interested in pursuing this as well.

It looks like each AI script is run from a table starting at 0x1D9BF4, where the AI bitfield determines which scripts to run in succession (eg. 0x7 is 0111 in binary which would run scripts 0, 1, and 2 from said table)

There are 18 or 19 scripts that do nothing, so we should be able to easily create our own AI scripts with knizz's and your contributions!

My one question is, do we know where the wild battle AI is set? For example, safari pokemon and roaming Pokemon have a separate AI bitfield; it would awesome to find this location and add in more flag checks for legendary battles, etc.
 

AkameTheBulbasaur

Akame Marukawa of Iyotono
409
Posts
10
Years
There are two scripts all the way at the very end of the table that seem to be for Wild Pokemon.

The first is at 0x1DBCA8 and it appears to check for conditions that would stop it from fleeing (Wrap, Mean Look, abilities etc.) and if they aren't there then it will run away. I think this is for roaming Pokemon who flee on the first turn.

Spoiler:


The second one was at 0x1DBCD6 and this appears to be for the Safari Zone, where the Pokemon is more likely to flee if it's at low health (less than 20%). The RunAway command is used twice, so maybe using it more means a higher chance of fleeing?
Spoiler:


There aren't any sort of move rating scripts here because Wild Pokemon just use random moves. Possibly editing these could make Wild Pokemon smarter, but I don't know how to make it work for only specific Pokemon (like legendaries and such). That would be interesting to look into though!

I'm not sure where the bitfield is set. The bitfield for each trainer is set in the data for each specific trainer (so gym leaders/E4 have different difficulty than some random Youngster). The bitfield for Safari Zone/Roamers might be set in some other routine that activates those battle types.
 
Last edited:
239
Posts
8
Years
  • Age 31
  • Seen Apr 15, 2024
Yeah I tried looking into where an AI script might be loaded for a roaming pokemon but didn't have any luck yet.

There is a function call at 0801e1b6 that runs through the type effectiveness table/levitate/etc checks and returns a bitfield value to r6 (0x0 is regular effectiveness, 0x1 for super effective, and 0x2 for not very effective). It does an OR operation so you could return 0x3 if the attack is super effective on the target's first type and resisted by the 2nd type.

I might be wrong but it looks like the JumpIfDamageBonusEquals calls a different function, so this routine could be used by wild pokemon as well rather than choosing a move completely at random? What is strange is that it is called within the accuracy check battle command.
 

AkameTheBulbasaur

Akame Marukawa of Iyotono
409
Posts
10
Years
I was messing around with the debugger yesterday because I was trying to see if changing the Battle Type to the Safari bit (0x80) would activate the script.

Long story short, it didn't. What it did was make the Safari Ball counter and menu options come up, but the Pokemon still behaves like it would have normally.

Which did end in the somewhat silly result of a wild Wurmple attacking my trainer sprite instead of my Ditto, but ultimately didn't quite give me what I was looking for.

So the script is probably called elsewhere. The AI Script table is only pointed to once in the ROM however.
 
Last edited:

Skeli

Lord of the Rings
300
Posts
10
Years
I rewrote the database for Jambo's Battle Script Pro to incorporate the commands for AI scripting. I've decompiled all of the vanilla scripts so it looks like I've worked out all the bugs. I still need to manually enter all of the descriptions for the commands, but that's not my top priority right now. Anyways, please enjoy AI Script Pro!

-Edit: It turns out that at this point the tool can only compile the first 1F commands or so. I need to finish the commands.bsh file so it actually compiles all the commands. Decompilation works fine though.

--Edit 2: It should be ready now.

--Edit 3: I've modified the source code to make it more compatible with AI scripting. As long as you don't devote any lines to just comments, or leave blank spaces in the middle of your #orgs (in between #org and #org is fine) it should work properly.
Spoiler:
 
Last edited:
794
Posts
10
Years
I hope you guys are aware that the commands have been known for a very long time and are decompiled in pokeruby/pokeemerald. All AI scripts have also been dumped in pokeruby.
 

Skeli

Lord of the Rings
300
Posts
10
Years
I hope you guys are aware that the commands have been known for a very long time and are decompiled in pokeruby/pokeemerald. All AI scripts have also been dumped in pokeruby.
Yes, but this thread will make it easier for any casual ROM hacker to make their own AI scripts without having to learn C.
 

AkameTheBulbasaur

Akame Marukawa of Iyotono
409
Posts
10
Years
I should probably clarify that the point of this thread isn't to introduce the concept of AI scripting or the commands. I'm aware they've been known for a while because the thread I referenced is from 2014. :D

The point was more to describe how they work in more detail (since I couldn't find anything else that really described how to use them in a script. That thread from 2014 was it to my knowledge), and also to introduce the new commands I'd written.

IMPORTANT! I have merged this post with the original one!

I should also announce that some of the commands (GetKindOfMove for example) ONLY WORK WITH THE FREE VAR INSTEAD OF THE MOVE VAR!!!!

I rewrote them so that they can use the move var, which is what is far more useful to check. They are under the Rewritten Commands tab in the original post.
 
Last edited:

Skeli

Lord of the Rings
300
Posts
10
Years
I've written a few new routines as well. They're rather simple, but they do the trick. Tbh I haven't tested them yet, but unless I made a mistake, they should work. I plan on testing them hopefully within the next week or so.

5E: GetPersonalCounter
Arguments: 1 Byte, 1 Word
This command takes the bank and the counter you wish to check (Magnet Rise, Telekinesis, etc.) for that Pokemon, and returns whatever number is in the counter. Basically it tells the AI if the counter isn't at 0, don't use the move.
Spoiler:

5F: GetTeamCounter
Arguments: 1 Byte, 1 Word
This command takes the bank and the team counter you wish to check (Tailwind, Lucky Chant, etc.) for that Pokemon's team, and returns whatever number is in the counter.
Spoiler:

60: GetFieldCounter
Arguments: 1 Word
This command takes the field counter you wish to check (Gravity, Trick Room, etc.) and returns whatever number is in the counter.
Spoiler:

61: GetTerrain
Arguments: None
This command returns the terrain type (Electric, Grassy, etc.). Make sure you replace the terrain offset in the code with the ram loc of your terrain type (if you've implemented terrains of course).
Spoiler:

62: GetSpecies
Arguments: 1 Byte
This command takes the bank and returns the species in the bank. 0 is the target, 1 is the user, 2 is the target's partner, and 3 is the user's partner. This can be used, for example, to see if the target is a Minior in Shield Form, and if it is, don't use any status 1 inducing moves.
Spoiler:

63: GetHeldItem
Arguments: 1 Byte
This command takes the bank and returns the held item of the Pokemon in that bank. 0 is the target, 1 is the user, 2 is the target's partner, and 3 is the user's partner. This can be used for Fling or Acrobatics to see if the attacker is holding an item.
Spoiler:

64: GetHeldItemPocket
Arguments: 1 Byte
This command takes the bank and returns the pocket of the held item of the Pokemon in that bank. 0 is the target, 1 is the user, 2 is the target's partner, and 3 is the user's partner. This is basically used to check if the Pokemon is holding a berry.
Spoiler:

Helper Function: GetPartnerBank
I'm going to be calling on this a lot so you should insert it somewhere and make a record of where you've inserted it.
Spoiler:
 
Last edited:

AkameTheBulbasaur

Akame Marukawa of Iyotono
409
Posts
10
Years
ATTENSHOON!!!!

Here I am including two things that are not scripting related but are important for the AI. The first is a Double Battle Logic routine (actually two of them), and the second is a method for getting the AI to switch out in a controlled manner.

Double Battle Target Logic + RAM Setting
First of all, any of the above routines which use the RAM addresses 02023D6D or 02023D6E need to be changed!!!

I was under the impression that those were the Attacker Partner and Defender Partner banks respectively

THEY ARE NOT!

Instead, you need to use a free RAM address to store those banks.

Here is a branch from the normal routine that sets the Attacker Partner and Defender Partner RAM, and nothing else. If you don't want any kind of target select logic then use this.

Spoiler:


Replace 0x2023D60 and 0x2023D61 with your choice of RAM if you don't want to/can't use those.

The next part is the same routine but with extra logic so that under certain conditions in Double Battles, the AI will target the partner. For example, of the partner has Volt Absorb, is low on health and the Attacker has a damaging Electric Type move, they will target the partner. This only sets the target, you have to get it to use the right move in an AI script!!! That is easy enough to do, however.

Fair warning! This is super duper long! I tried to shorten it as much as possible and it's still really long. Sorry about that!

Spoiler:


I bolded the parts with the RAM for the Attacker and Defender Partners. You can change this to any piece of free RAM you want.

AI Switch Logic
So, there may be a better way to do this, but this is the way that I have come up with an gotten to work. It involves a little work, but once it's done it should work seamlessly.

I will go over this step by step:

1. Find a move to replace or make a new move. Remember its index number in hex.

2. Find a Battle Script you're not using, or branch off of an existing one. How you get the move to use the script is unimportant, as long as that specific move uses this script:
Spoiler:


3. Insert this routine:
Spoiler:


4. Then insert this routine:
Spoiler:


Essentially what you're doing here is having a move that simply makes the AI switch out. It doesn't announce "Pokemon used Move!" or anything, so it doesn't look like an attack when it secretly is. The advantages of using this method were, of course, that I didn't have to learn how the preexisting routine works because I'm doing something completely different.

More importantly than me being lazy, this method lets you decide when the AI should switch within an AI script. Here's how to set that up.

1. Insert this modified version of the AddToViabilityScore routine:
Spoiler:


2. When you want to AI to automatically switch, just write "AddToViabilityScore 0x5E" and no matter what, it will switch. You can change 0x5E to something else in the routine if you want.

The above routine will also switch if all of the Pokemon's moves are below a certain viability score (so it will switch if all of its moves have no effect, for instance). I bolded the line to change.

It will not switch if it is unable to. For example, if the opponent has Shadow Tag, then the AI won't use the switch move even if you set the Switch Score. It will just use one of its actual moves instead.

Here are a few more things you will probably want to do:
1. Edit the Metronome Ban List: The Metronome Ban List is located at 0x2507E8. You'll probably have to replace a move that's already there, but to be honest, that's not too big of a deal. Does Protect really need to be banned from Metronome? Anyway, I digress. You won't want Metronome to call the switch move, so you should add it to the Ban List somehow.

2. Sketch: You also don't want Sketch to be able to learn the move, so insert this routine so that Sketch will fail if the Switch Move was the one last used. Not sure if it would fail anyway because the Pokemon switched out, but better safe than sorry.
Spoiler:


You'll probably also want to do a similar thing to the above with Mimic.
 
Last edited:
82
Posts
6
Years
Hey akame, I'm somewhat new to hacking to I'm trying to understand everything going on here.

I was able to get the inverse battle checker to work but that's a callasm outside of the battle.

How do I get spikes layer or really anything else to work? How does the ai check before using a move? Also, what are macros used for and what are the arguments you speak of? If this is too much to explain, do you have a thread that I can learn about all this? I'm trying to make a difficult fire red hack so I'd like to utilize your research and work. Thank you!
 

AkameTheBulbasaur

Akame Marukawa of Iyotono
409
Posts
10
Years
Hey akame, I'm somewhat new to hacking to I'm trying to understand everything going on here.
!

Whoops! Sorry I totally didn't see this until now!

Basically each of the opponent's moves has a score of 100 by default. The purpose of the AI scripts is to add or subtract from that score based on certain conditions. You use the commands in the scripts (kind of like you would in normal NPC scripting).

So for example, if you wanted to make a script that would prevent the AI from using Hypnosis when the foe already has a status, it would look like this.

Main:
JumpIfMoveScriptEquals 0x1 CauseSleep
ReturnToBattle

CauseSleep:
JumpIfStatus1Equals Defender 0x000000FF Decrease32
ReturnToBattle

Decrease32:
AddToViabilityScore 0xE0
ReturnToBattle


Each script is run for each move (so each script is run a total of four times maximum). Once all the scripts are run, the AI picks the move that has the highest score (I presume that if one or more have the same score, it picks one randomly between them).

Which scripts are run is determined by a value in the Trainer Data. It's a bitfield, so if you want script 1, the value is 0x1 (1 in binary). If you want scripts 1-3, the value is 0x7 (111 in binary).

Hopefully this is helpful. If you have other questions feel free to PM me.
 
116
Posts
16
Years
ATTENSHOON!!!!

Here I am including two things that are not scripting related but are important for the AI. The first is a Double Battle Logic routine (actually two of them), and the second is a method for getting the AI to switch out in a controlled manner.

Double Battle Target Logic + RAM Setting
First of all, any of the above routines which use the RAM addresses 02023D6D or 02023D6E need to be changed!!!

I was under the impression that those were the Attacker Partner and Defender Partner banks respectively

THEY ARE NOT!

Instead, you need to use a free RAM address to store those banks.

Do you mind to tell me that are the Attacker Partner and Defender Partner same with the APartner and DPartner? I am very confusing about it.
 
Last edited:

Lance Koijer 2.0

Lance Koijer
105
Posts
6
Years
If you mean whether or not APartner and Attacker Partner mean the same thing, then yes they do. APartner is just easier to write.
Hello Akame! I found this research very interesting and got most of it. However, I am confused on the AI Switching. Does this include player being able to force switch? Same concept is applied. I'm sorry if this has been mentioned on your post already, I might have missed it.

EDIT: Another question, let's say I'm going to create an AI Script based on the commands listed above, in what particular part of the ROM I'm going to insert it? Does it have a table of scripts that will be checked by a routine somewhere in ROM then generates all possible script or it needs a hook?
 
Last edited:
116
Posts
16
Years
If you mean whether or not APartner and Attacker Partner mean the same thing, then yes they do. APartner is just easier to write.

Oh, I see. Are all the routines that use 0x02023D6D and 0x02023D6E or 0x02023D60 and 0x02023D61 as the APartner and DPartner RAM respectively must replace with the free RAM addresses?
 
Last edited:

AkameTheBulbasaur

Akame Marukawa of Iyotono
409
Posts
10
Years
Oh, I see. Are all the routines that use 0x02023D6D and 0x02023D6E or 0x02023D60 and 0x02023D61 as the APartner and DPartner RAM respectively must replace with the free RAM addresses?

Correct. I used 0x02023D60 and 0x02023D61 as my free RAM, but you can pick anything as long as it's free. If I remember correctly, Mr. DollSteak used those two RAM addresses I mentioned previously in something, so if you're using that then you will need to pick new ones. If you're not using anything else that uses those, then those two are fine.
 

AkameTheBulbasaur

Akame Marukawa of Iyotono
409
Posts
10
Years
Hello Akame! I found this research very interesting and got most of it. However, I am confused on the AI Switching. Does this include player being able to force switch? Same concept is applied. I'm sorry if this has been mentioned on your post already, I might have missed it.

EDIT: Another question, let's say I'm going to create an AI Script based on the commands listed above, in what particular part of the ROM I'm going to insert it? Does it have a table of scripts that will be checked by a routine somewhere in ROM then generates all possible script or it needs a hook?

I'm assuming what you mean by force switch is setting up a situation where the AI has to switch and will always switch. There are two ways to do that. The easiest way is to use the modified AddToViabilityScore command and write "AddToViabilityScore 0x5E." This will guarantee that the AI will switch (provided it actually can switch that is). This is the way you actually have control over in AI scripts.

The other way that the AI will switch with this is if all of their moves are ineffective. You can kind of influence this through AI scripts but it's easier to just use "AddToViabilityScore 0x5E" if you want a guaranteed switch.

For example, if you want the AI to always switch out if they are badly poisoned:

PoisonCheck:
JumpIfStatus1Equals Attacker0x00000080 Switch1
ReturnToBattle

Switch1:
AddToViabilityScore 0x5E
ReturnToBattle

As for your second question, there is a table at 0x1D9BF4 that has a series of pointers to all the AI scripts. The way you get trainers to use these scripts is determined by a value in the trainer data. I forget where it is in other editors, but in HopelessTrainerEditor, it's right underneath the name (if I remember correctly).

The way it works is that the game reads that number in binary and uses the script numbers indicated by the positions of the ones in that number. So for example, if you put 7 for that value (which for a while was assumed to be the maximum value), that trainer would use scripts 1, 2 and 3 (because 7 in binary is 111).

Personally, I have had the most luck using 7 scripts and no more (so a trainer value of 127), but theoretically you could have up to 16(? I think this is the actual maximum since if I remember right the AI value is a halfword). The table I gave you has 32 entries though, so I don't know.
 
116
Posts
16
Years
Correct. I used 0x02023D60 and 0x02023D61 as my free RAM, but you can pick anything as long as it's free. If I remember correctly, Mr. DollSteak used those two RAM addresses I mentioned previously in something, so if you're using that then you will need to pick new ones. If you're not using anything else that uses those, then those two are fine.

Okay. Thanks for answering. In the JumpIfDamageBonusEquals AI commands, why Fairy doesn't use the own Fairy move instead of the Metronome?
 
Back
Top