Advertiser Content

Research Trainer AI Command Research

Started by AkameTheBulbasaur November 25th, 2017 1:41 PM
  • 5265 views
  • 32 replies

AkameTheBulbasaur

Akame Marukawa of Iyotono

Age 22
Male
A place :D
Seen 5 Days Ago
Posted July 19th, 2019
361 posts
6.4 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!

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 has all the rewritten command routines in it.

The third 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:
/*Put XX XX XX 08 at 0x3F5664*/
.text
.align 2
.thumb
.thumb_func

Main:
push {r4, lr}
ldr r4, .AICursor
ldr r3, [r4]
add r3, r3, #0x4
str r3, [r4]
sub r3, r3, #0x4
ldrb r1, [r3, #0x1]
ldrb r0, [r3, #0x2]
lsl r0, r0, #0x8
orr r1, r0
ldrb r0, [r3, #0x3]
lsl r0, r0, #0x10
orr r1, r0
ldrb r0, [r3, #0x4]
lsl r0, r0, #0x18
orr r1, r0
bl DoRoutine
pop {r4}
pop {r0}
bx r0

DoRoutine:
bx r1

.align 2
.AICursor: .word 0x02039A00


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.
"The human sacrificed himself, to save the Pokemon. I pitted them against each other, but not until they set aside their differences did I see the true power they all share deep inside. I see now that the circumstances of one's birth are irrelevant; it is what you do with the gift of life that determines who you are." -Mewtwo
Male
Seen 2 Hours Ago
Posted 5 Hours Ago
123 posts
3.6 Years
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

Age 22
Male
A place :D
Seen 5 Days Ago
Posted July 19th, 2019
361 posts
6.4 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:

0x81DBCA8
Main:
JumpIfStatus2Equals Attacker 0000E000 Return1
JumpIfStatsu2Equals Attacker 04000000 Return1
GetAbility Defender
JumpIfByteEquals ShadowTag Return1
GetAbility Attacker
JumpIfByteEquals Levitate RunAway1
GetAbility Defender
JumpIfByteEquals ArenaTrap Return1

0x81DBCD4
RunAway1:
RunAway

0x8DBCD5
Return1:
ReturnToBattle


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:

0x81DBCD6
Main:
JumpRandomUnknown RunAway1
SafariZone

0x81DBCDC
RunAway1:
RunAway
JumpIfHealthEquals Defender 0x14 RunAway2
JumpIfHealthLessThan Defender 0x14 RunAway2
ReturnToBattle

0x81DBCEC
RunAway2:
RunAway
ReturnToBattle


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.
"The human sacrificed himself, to save the Pokemon. I pitted them against each other, but not until they set aside their differences did I see the true power they all share deep inside. I see now that the circumstances of one's birth are irrelevant; it is what you do with the gift of life that determines who you are." -Mewtwo
Male
Seen 2 Hours Ago
Posted 5 Hours Ago
123 posts
3.6 Years
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

Age 22
Male
A place :D
Seen 5 Days Ago
Posted July 19th, 2019
361 posts
6.4 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.
"The human sacrificed himself, to save the Pokemon. I pitted them against each other, but not until they set aside their differences did I see the true power they all share deep inside. I see now that the circumstances of one's birth are irrelevant; it is what you do with the gift of life that determines who you are." -Mewtwo

Skeli-

Lord of the Rings

Age 20
Male
Canada
Seen 2 Days Ago
Posted 4 Days Ago
227 posts
5.5 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:
Eg. You can do this:
#org @Start
something
returntobattle

#org @Whatever
returntobattle
But not this:
#org @start
whatever
something
                                 NOOO!
returntobattle

//@@@@@@@@@@ NOOO!

#org @somethingelse
returntobattle
Pokemon Unbound

Skeli-

Lord of the Rings

Age 20
Male
Canada
Seen 2 Days Ago
Posted 4 Days Ago
227 posts
5.5 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.
Pokemon Unbound

AkameTheBulbasaur

Akame Marukawa of Iyotono

Age 22
Male
A place :D
Seen 5 Days Ago
Posted July 19th, 2019
361 posts
6.4 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.
"The human sacrificed himself, to save the Pokemon. I pitted them against each other, but not until they set aside their differences did I see the true power they all share deep inside. I see now that the circumstances of one's birth are irrelevant; it is what you do with the gift of life that determines who you are." -Mewtwo

Skeli-

Lord of the Rings

Age 20
Male
Canada
Seen 2 Days Ago
Posted 4 Days Ago
227 posts
5.5 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:
.thumb
.global AI_GetPersonalCounter

@GetPersonalCounter BANK COUNTER
Main:
push {r4-r5,lr}
ldr r5, .AICursor
ldr r0, [r5]
ldrb r0, [r0, #0x1]
bl GetBattleSide

GetCounter:
ldr r1, [r5]
ldrb r2, [r1, #0x3]
ldrb r3, [r1, #0x4]
ldrb r4, [r1, #0x5]
ldrb r1, [r1, #0x2]
lsl r2, #0x8
lsl r3, #0x10
lsl r4, #0x18
orr r1, r2
orr r1, r3
orr r1, r4

LoadCounter:
add r1, r0
ldrb r1, [r1]

Store:
ldr r0, .Resources
ldr r0, [r0]
ldr r0, [r0, #0x14]
str r1, [r0, #0x8]

Return:
ldr r0, [r5]
add r0, #0x6
str r0, [r5]
pop {r4-r5,pc}

GetBattleSide:
ldr r1, =0x8016E25
bx r1

.align 2
.Resources:	.word 0x02023FF4
.AICursor:	.word 0x02039A00


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:
.thumb
.global AI_GetTeamCounter

@GetTeamCounter BANK COUNTER
Main:
push {r4-r5,lr}
ldr r5, .AICursor
ldr r0, [r5]
ldrb r0, [r0, #0x1]
bl GetBattleSide
mov r1, #0x1
and r0, r1

GetCounter:
ldr r1, [r5]
ldrb r2, [r1, #0x3]
ldrb r3, [r1, #0x4]
ldrb r4, [r1, #0x5]
ldrb r1, [r1, #0x2]
lsl r2, #0x8
lsl r3, #0x10
lsl r4, #0x18
orr r1, r2
orr r1, r3
orr r1, r4

LoadCounter:
add r1, r0
ldrb r1, [r1]

Store:
ldr r0, .Resources
ldr r0, [r0]
ldr r0, [r0, #0x14]
str r1, [r0, #0x8]

Return:
ldr r0, [r5]
add r0, #0x6
str r0, [r5]
pop {r4-r5,pc}

GetBattleSide:
ldr r1, =0x8016E25
bx r1

.align 2
.Resources:	.word 0x02023FF4
.AICursor:	.word 0x02039A00


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:
.thumb
.global AI_GetFieldCounter

@GetFieldCounter COUNTER
GetCounter:
push {r4,lr}
ldr r4, .AICursor
ldr r0, [r4]
ldrb r1, [r0, #0x2]
ldrb r2, [r0, #0x3]
ldrb r3, [r0, #0x4]
ldrb r0, [r0, #0x1]
lsl r1, #0x8
lsl r2, #0x10
lsl r3, #0x18
orr r0, r1
orr r0, r2
orr r0, r3

LoadByte:
ldrb r1, [r0]

Store:
ldr r0, .Resources
ldr r0, [r0]
ldr r0, [r0, #0x14]
str r1, [r0, #0x8]

Return:
ldr r0, [r4]
add r0, #0x5
str r0, [r4]
pop {r4,pc}

.align 2
.Resources:	.word 0x02023FF4
.AICursor:	.word 0x02039A00


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:
.thumb
.global AI_GetTerrain

LoadTerrain:
push {r4,lr}
ldr r0, .TerrainLoc
ldrb r0, [r0]

Store:
ldr r1, .Resources
ldr r1, [r1]
ldr r1, [r1, #0x14]
str r0, [r1, #0x8]

Return:
ldr r4, .AICursor
ldr r0, [r4]
add r0, #0x1
str r0, [r4]
pop {r4,pc}

.align 2
.TerrainLoc:	.word 0x02AAAAAA
.Resources:	.word 0x02023FF4
.AICursor:	.word 0x02039A00


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:
.thumb
.global AI_GetSpecies

@GetSpecies BANK
LoadBank:
push {r4,lr}
ldr r4, .AICursor
ldr r0, [r4]
ldrb r0, [r0, #0x1]
cmp r0, #0x2
bge PartnerCheck
bl GetBattleSide
b LoadBattleData

PartnerCheck:
sub r0, #0x2
bl GetBattleSide
bl GetPartnerBank

LoadBattleData:
mov r1, r0
ldr r0, .BattleData
mov r2, #0x58
mul r1, r2
add r0, r1
ldrh r0, [r0]

Store:
ldr r1, .Resources
ldr r1, [r1]
ldr r1, [r1, #0x14]
str r0, [r1, #0x8]

Return:
ldr r0, [r4]
add r0, #0x2
str r0, [r4]
pop {r4,pc}

GetBattleSide:
ldr r1, =0x8016E25
bx r1

GetPartnerBank:
ldr r1, =0x8XXXXXX + 1
bx r1

.align 2
.BattleData:	.word 0x2023BE4
.Resources:	.word 0x2023FF4
.AICursor:	.word 0x2039A00


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:
.thumb
.global AI_GetHeldItem

@GetHeldItem BANK
LoadBank:
push {r4,lr}
ldr r4, .AICursor
ldr r0, [r4]
ldrb r0, [r0, #0x1]
cmp r0, #0x2
bge PartnerCheck
bl GetBattleSide
b LoadBattleData

PartnerCheck:
sub r0, #0x2
bl GetBattleSide
bl GetPartnerBank

LoadBattleData:
mov r1, r0
ldr r0, .BattleData
mov r2, #0x58
mul r1, r2
add r0, r1
ldrh r0, [r0]

Store:
ldr r1, .Resources
ldr r1, [r1]
ldr r1, [r1, #0x14]
str r0, [r1, #0x8]

Return:
ldr r0, [r4]
add r0, #0x2
str r0, [r4]
pop {r4,pc}

GetBattleSide:
ldr r1, =0x8016E25
bx r1

GetPartnerBank:
ldr r1, =0x8XXXXXX + 1
bx r1

.align 2
.BattleData:	.word 0x2023C12
.Resources:	.word 0x2023FF4
.AICursor:	.word 0x2039A00


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:
.thumb
.global AI_GetHeldItemPocket

@GetHeldItemPocket BANK
LoadBank:
push {r4,lr}
ldr r4, .AICursor
ldr r0, [r4]
ldrb r0, [r0, #0x1]
cmp r0, #0x2
bge PartnerCheck
bl GetBattleSide
b LoadBattleData

PartnerCheck:
sub r0, #0x2
bl GetBattleSide
bl GetPartnerBank

LoadBattleData:
mov r1, r0
ldr r0, .BattleData
mov r2, #0x58
mul r1, r2
add r0, r1
ldrh r0, [r0]

GetPocketNumber:
bl GetPocket

Store:
ldr r1, .Resources
ldr r1, [r1]
ldr r1, [r1, #0x14]
str r0, [r1, #0x8]

Return:
ldr r0, [r4]
add r0, #0x2
str r0, [r4]
pop {r4,pc}

GetBattleSide:
ldr r1, =0x8016E25
bx r1

GetPocket:
ldr r1, =0x809A9D9
bx r1

GetPartnerBank:
ldr r1, =0x8XXXXXX + 1
bx r1

.align 2
.BattleData:	.word 0x2023C12
.Resources:	.word 0x2023FF4
.AICursor:	.word 0x2039A00


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:
.thumb
.global GetPartnerBankBL

GetPartnerBank:
	push {lr}
	cmp r0, #0x0
	beq P1
	cmp r0, #0x1
	beq O1
	cmp r0, #0x2
	beq P2
	cmp r0, #0x3
	beq O2
P1: 	mov r0, #0x2
	pop {pc}
O1: 	mov r0, #0x3
	pop {pc}
P2: 	mov r0, #0x0
	pop {pc}
O2: 	mov r0, #0x1
	pop {pc}
Pokemon Unbound

AkameTheBulbasaur

Akame Marukawa of Iyotono

Age 22
Male
A place :D
Seen 5 Days Ago
Posted July 19th, 2019
361 posts
6.4 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:
/*Put 00 48 00 47 XX XX XX 08 at 0xC6E28*/
.text
.align 2
.thumb
.thumb_func

SetAttacker:
ldr r1, .Attacker
ldr r0, .Buffer
ldrb r2, [r0]
strb r2, [r1]

SetPartner:
ldr r1, .APartner
cmp r2, #0x1
beq Set3
mov r0, #0x1
strb r0, [r1]
b DoubleCheck

Set3:
mov r0, #0x3
strb r0, [r1]

DoubleCheck:
ldr r0, .BattleType
ldr r0, [r0]
mov r1, #0x1
and r0, r1
cmp r0, #0x0
beq Single

Double:
ldr r4, .Defender
bl RNG
mov r3, #0x2
and r0, r3
strb r0, [r4]
push {r4}
ldr r4, .DPartner
cmp r0, #0x0
beq SetTwo
cmp r0, #0x1
beq SetThree
cmp r0, #0x2
beq SetZero
cmp r0, #0x3
beq SetOne

SetTwo:
mov r0, #0x2
b Return

SetZero:
mov r0, #0x0
b Return

SetOne:
mov r0, #0x1
b Return

SetThree:
mov r0, #0x3
b Return

Return:
strb r0, [r4]
pop {r4}
ldr r0, .Return
bx r0

RNG:
ldr r3, .GetRNG
bx r3

Single:
ldr r0, .Defender1
mov r1, #0x1
eor r1, r2
strb r1, [r0]
ldr r0, .DPartner1
cmp r1, #0x0
beq SetTwo
cmp r1, #0x1
beq SetThree
cmp r1, #0x2
beq SetZero
mov r1, #0x1
strb r1, [r0]
b Return

SetTwo:
mov r1, #0x2
strb r1, [r0]
b Return

SetThree:
mov r1, #0x3
strb r1, [r0]
b Return

SetZero:
mov r1, #0x0
strb r1, [r0]
b Return

ReturnS:
ldr r0, .BattleType
ldr r1, [r0]
ldr r0, .Return2
bx r0

.align 2
.Attacker: .word 0x2023D6B
.Defender: .word 0x2023D6C
.APartner: .word 0x2023D60
.DPartner: .word 0x2023D61

.Buffer: .word 0x2023BC4
.Return: .word 0x80C6E47
.Return2: .word 0x80C6E89
.BattleType: .word 0x2022B4C
.GetRNG: .word 0x8044EC9


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:

/*Insert 00 48 00 47 XX XX XX 08 at 0xC6E28*/
.text
.align 2
.thumb
.thumb_func

SetAttacker:
ldr r1, .Attacker1
ldr r0, .Buffer
ldrb r2, [r0]
strb r2, [r1]

SetPartner:
ldr r1, .APartner1
cmp r2, #0x1
beq Set3
mov r0, #0x1
strb r0, [r1]
b GetBattleType

Set3:
mov r0, #0x3
strb r0, [r1]

GetBattleType:
ldr r0, .BattleType
ldr r0, [r0]
mov r1, #0x1 /*Double*/
and r0, r1
cmp r0, #0x0
beq Single

Double:
push {r0-r7}
ldr r0, .Attacker1
ldrb r0, [r0]
bl GetHealth
cmp r3, #0x32
ble SetDefender

Checks:
bl CheckHealingMove
cmp r4, #0x1
beq TargetPartner
cmp r4, #0x2
beq SkipHealingAbilities

CheckAbilities:
ldr r0, .APartner1
ldrb r0, [r0]
mov r1, #0x20
bl UserDataByte
cmp r2, #0xA
beq VoltAbsorb
cmp r2, #0xB
beq WaterAbsorb

SkipHealingAbilities:
ldr r0, .APartner1
ldrb r0, [r0]
mov r1, #0x20
bl UserDataByte
cmp r2, #0x12
beq FlashFire
cmp r2, #0x14
beq OwnTempo
cmp r2, #0x1F
beq Lightningrod
cmp r2, #0x3E
beq Guts
cmp r2, #0x3F
beq Guts
b CheckYourAbility


CheckYourAbility:
mov r5, #0x1
ldr r0, .Attacker1
ldrb r0, [r0]
bl GetHealth
cmp r3, #0x4B
bge CMPFF2
ldr r0, .Attacker1
ldrb r0, [r0]
mov r1, #0x20
bl UserDataByte
cmp r2, #0xA
beq VoltAbsorb
cmp r2, #0xB
beq WaterAbsorb

CMPFF2:
ldr r0, .Attacker1
ldrb r0, [r0]
mov r1, #0x20
bl UserDataByte
cmp r2, #0x12
beq FlashFire
cmp r2, #0x1F
beq Lightningrod
b CheckItems

VoltAbsorb:
mov r7, #0xD
bl TypeCheck
b Next

WaterAbsorb:
mov r7, #0xD
bl TypeCheck
b Next

FlashFire:
bl FireCheck
b Next

Guts:
bl Guts2
b Next

Lightningrod:
bl RodCheck
b Next

OwnTempo:
bl Tempo

Next:
cmp r4, #0x1
beq TargetPartner
b CheckItems

CheckItems:
bl ItemCheck
cmp r4, #0x1
beq TargetPartner
b SetDefender

TargetPartner:
ldr r1, .APartner1
ldrb r1, [r1]
ldr r0, .Defender1
strb r1, [r0]
ldr r1, .Attacker1
ldrb r1, [r1]
ldr r0, .DPartner1
strb r1, [r0]
b Return

SetDefender:
bl RNG
mov r3, #0x2
and r0, r3
mov r4, r0
ldr r0, .Defender1
strb r4, [r0]
ldr r0, .DPartner1
cmp r4, #0x0
beq SetTwo
cmp r4, #0x2
beq SetZero

SetTwo:
mov r4, #0x2
strb r4, [r0]
b Return

SetZero:
mov r4, #0x0
strb r4, [r0]

Return:
pop {r0-r7}
ldr r0, .ReturnD
bx r0

Single:
ldr r0, .Defender1
mov r1, #0x1
eor r1, r2
strb r1, [r0]
ldr r0, .DPartner1
cmp r1, #0x0
beq SetTwo
cmp r1, #0x1
beq SetThree
cmp r1, #0x2
beq SetZero
mov r1, #0x1
strb r1, [r0]
b Return

SetTwo:
mov r1, #0x2
strb r1, [r0]
b Return

SetThree:
mov r1, #0x3
strb r1, [r0]
b Return

SetZero:
mov r1, #0x0
strb r1, [r0]
b Return

ReturnS:
ldr r0, .BattleType
ldr r1, [r0]
ldr r0, .ReturnS
bx r0


.align 2
.ReturnD: .word 0x080C6E85
.ReturnS: .word 0x080C6E89
.BattleType: .word 0x02022B4C
.Attacker1: .word 0x02023D6B
.Defender1: .word 0x02023D6C
.APartner1: .word 0x02023D60
.DPartner1: .word 0x02023D61
.Buffer: .word 0x02023BC4

/*Checks For Healing Move 0x9D*/

.align 2

CheckHealingMove:
push {r0-r3, r6, lr}
ldr r0, .APartner2
ldrb r0, [r0]
bl GetHealth
cmp r3, #0x32
bgt SetR4Two
mov r6, #0x1

GetMoveScripts:
mov r1, #0xB
add r1, r1, r6
ldr r0, .Attacker2
ldrb r0, [r0]
bl UserDataHWord
mov r0, r2
mov r1, #0x0
bl GetAttackData
cmp r2, #0x9D
beq CheckPP1

Loop:
add r6, r6, #0x1
cmp r6, #0x4
bge SetR4Zero1
b GetMoveScripts

CheckPP1:
mov r1, #0x23
add r1, r1, r6
ldr r0, .Attacker2
ldrb r0, [r0]
bl UserDataByte
cmp r2, #0x0
beq SetR4Zero1
b SetR4One1

SetR4One1:
mov r4, #0x1
b Return1

SetR4Two:
mov r4, #0x2
b Return1

SetR4Zero1:
mov r4, #0x0

Return1:
pop {r0-r3, r6, pc}

.align 2
.Attacker2: .word 0x02023D6B
.APartner2: .word 0x02023D60

/*Volt and Water Absorb Checks*/
.align 2

TypeCheck:
push {r0-r3, r6, lr}
mov r6, #0x1

MoveChecks:
mov r1, #0xB
add r1, r1, r6
ldr r0, .Attacker3
ldrb r0, [r0]
bl UserDataHWord
mov r0, r2
mov r1, #0x2
bl GetAttackData
cmp r2, r7
bne Loop2
mov r1, #0x6
bl GetAttackData
cmp r2, #0x0
bne Loop2
cmp r7, #0xD
beq ParCheck

Loop2:
add r6, r6, #0x1
cmp r6, #0x4
bgt SetR4Zero2
b MoveChecks

CheckPP2:
mov r1, #0x23
add r1, r1, r6
ldr r0, .Attacker3
ldrb r0, [r0]
bl UserDataByte
cmp r2, #0x0
beq SetR4Zero2
b SetR4One2

ParCheck:
mov r1, #0x0
bl GetAttackData
cmp r2, #0x43
bne CheckPP2
cmp r5, #0x1
beq CheckYou3
b TypeChecking3

CheckYou3:
ldr r0, .Attacker3
ldrb r0, [r0]
b THING3

TypeChecking3:
ldr r0, .APartner3
ldrb r0, [r0]

THING3:
mov r1, #0x21
bl UserDataByte
cmp r2, r7
beq Loop2
mov r1, #0x22
bl UserDataByte
cmp r2, r7
beq Loop2
b CheckPP2

SetR4Zero2:
mov r4, #0x0
b Return2

SetR4One2:
mov r4, #0x1

Return2:
pop {r0-r3, r6, pc}

.align 2
.Attacker3: .word 0x02023D6B
.APartner3: .word 0x02023D60

/*Flash Fire Check*/
.align 2

FireCheck:
push {r0-r3, r6, lr}
mov r1, #0x4C
bl UserDataWord
mov r1, #0x20
and r2, r1
cmp r2, #0x0
bne SetR4Zero4
ldr r2, .FlashFire
lsl r1, r0, #0x2
add r2, r2, r1
ldr r2, [r2]
mov r1, #0x1
and r1, r2
cmp r1, #0x0
bne SetR4Zero4
mov r7, #0xA
mov r6, #0x1
b MoveChecks2

MoveChecks2:
mov r1, #0xB
add r1, r1, r6
ldr r0, .Attacker4
ldrb r0, [r0]
bl UserDataHWord
mov r0, r2
mov r1, #0x2
bl GetAttackData
cmp r2, #0xA
bne Loop4
mov r1, #0x6
bl GetAttackData
cmp r2, #0x0
bne Loop4
b Burn

Loop4:
add r6, r6, #0x1
cmp r6, #0x4
bgt SetR4Zero4
b MoveChecks2

CheckPP4:
mov r1, #0x23
add r1, r1, r6
ldr r0, .Attacker4
ldrb r0, [r0]
bl UserDataByte
cmp r2, #0x0
beq SetR4Zero4
b SetR4One4

Burn:
mov r1, #0x0
bl GetAttackData
cmp r2, #0xA7
cmp r5, #0x1
beq CheckYou4
b TypeChecking4

CheckYou4:
ldr r0, .Attacker4
ldrb r0, [r0]
b THING4

TypeChecking4:
ldr r0, .APartner4
ldrb r0, [r0]

THING4:
mov r1, #0x21
bl UserDataByte
cmp r2, #0xA
beq Loop4
mov r1, #0x22
bl UserDataByte
cmp r2, #0xA
beq Loop4
b CheckPP4

SetR4Zero4:
mov r4, #0x0
b Return4

SetR4One4:
mov r4, #0x1

Return4:
pop {r0-r3, r6, pc}

.align 2
.FlashFire: .word 0x02000300
.Attacker4: .word 0x02023D6B
.APartner4: .word 0x02023D60

/*Lightningrod Check*/
.align 2

RodCheck:
push {r0-r3, r6, lr}
mov r1, #0x19
bl UserDataByte
mov r3, r2
mov r1, #0x1C
bl UserDataByte
cmp r3, #0xC
bne Next2
cmp r2, #0xC
beq SetR4Zero9
b Next2

Next2:
mov r6, #0x1

MoveChecks8:
mov r1, #0xB
add r1, r1, r6
ldr r0, .Attacker8
ldrb r0, [r0]
bl UserDataHWord
mov r0, r2
mov r1, #0x2
bl GetAttackData
cmp r2, #0xD
bne Loop8
mov r1, #0x6
bl GetAttackData
cmp r2, #0x0
bne Loop8
b ParCheck2

Loop8:
add r6, r6, #0x1
cmp r6, #0x4
bge SetR4Zero9
b MoveChecks8


CheckPP8:
mov r1, #0x23
add r1, r1, r6
ldr r0, .Attacker8
ldrb r0, [r0]
bl UserDataByte
cmp r2, #0x0
beq SetR4Zero9
b SetR4One8

ParCheck2:
mov r1, #0x0
bl GetAttackData
cmp r2, #0x43
bne CheckPP8
cmp r5, #0x1
beq CheckYou8
b TypeChecking8

CheckYou8:
ldr r0, .Attacker8
ldrb r0, [r0]
b THING8

TypeChecking8:
ldr r0, .APartner8
ldrb r0, [r0]

THING8:
mov r1, #0x21
bl UserDataByte
cmp r2, #0xD
beq Loop8
mov r1, #0x22
bl UserDataByte
cmp r2, #0xD
beq Loop8
b CheckPP8

SetR4Zero9:
mov r4, #0x0
b Return8

SetR4One8:
mov r4, #0x1

Return8:
pop {r0-r3, r6, pc}

.align 2
.Attacker8: .word 0x02023D6B
.APartner8: .word 0x02023D60

/*Guts Checks*/
.align 2

Guts2:
push {r0-r3, r6, lr}
mov r1, #0x4C
bl UserDataWord
mov r1, #0xFF
and r2, r1
cmp r2, #0x0
bne SetR4Zero5
ldr r0, .Attacker5
ldrb r0, [r0]
bl GetSide
mov r2, #0x1
and r2, r0
ldr r0, .Status4
lsl r2, r2, #0x1
add r2, r2, r0
ldrh r0, [r2]
mov r1, #0x20
and r1, r0
cmp r0, #0x0
bne SetR4Zero5
mov r6, #0x1

StatusMoveLoop:
mov r1, #0xB
add r1, r1, r6
ldr r0, .Attacker5
ldrb r0, [r0]
bl UserDataHWord
mov r0, r2
mov r1, #0x0
bl GetAttackData
cmp r2, #0x21
beq PoisonCheck
cmp r2, #0x42
beq PoisonCheck
cmp r2, #0x43
beq ParalyzeCheck
cmp r2, #0xA7
beq BurnCheck
b Loop5

Loop5:
add r6, r6, #0x1
cmp r6, #0x4
bgt SetR4Zero5
b StatusMoveLoop

PoisonCheck:
ldr r0, .APartner5
ldrb r0, [r0]
mov r1, #0x21
bl UserDataByte
cmp r2, #0x3
beq Loop5
cmp r2, #0x8
beq Loop5
mov r1, #0x22
bl UserDataByte
cmp r2, #0x3
beq Loop5
cmp r2, #0x8
beq Loop5
mov r1, #0x2E
bl UserDataHWord
cmp r2, #0x87
beq Loop5
cmp r2, #0x56
beq Loop5
cmp r2, #0x8D
beq Loop5
b CheckPP5

BurnCheck:
ldr r0, .APartner5
ldrb r0, [r0]
mov r1, #0x21
bl UserDataByte
cmp r2, #0xA
beq Loop5
mov r1, #0x22
bl UserDataByte
cmp r2, #0xA
beq Loop5
mov r1, #0x2E
bl UserDataHWord
cmp r2, #0x88
beq Loop5
cmp r2, #0x56
beq Loop5
cmp r2, #0x8D
beq Loop5
b CheckPP5

ParalyzeCheck:
ldr r0, .APartner5
ldrb r0, [r0]
mov r1, #0x21
bl UserDataByte
cmp r2, #0xD
beq Loop5
mov r1, #0x22
bl UserDataByte
cmp r2, #0xD
beq Loop5
mov r1, #0x2E
bl UserDataHWord
cmp r2, #0x85
beq Loop5
cmp r2, #0x8D
beq Loop5
cmp r2, #0x56
beq Loop5
b CheckPP5

CheckPP5:
mov r1, #0x23
add r1, r1, r6
ldr r0, .Attacker5
ldrb r0, [r0]
bl UserDataByte
cmp r2, #0x0
beq SetR4Zero5
b SetR4One5

SetR4Zero5:
mov r4, #0x0
b Return5

SetR4One5:
mov r4, #0x1

Return5:
pop {r0-r3, r6, pc}

.align 2
.Attacker5: .word 0x02023D6B
.APartner5: .word 0x02023D60
.Status4: .word 0x02023DDE

/*Own Tempo Check*/
.align 2

Tempo:
push {r0-r3, r6, lr}
mov r1, #0x50
bl UserDataWord
mov r1, #0x7
And r2, r1
cmp r2, #0x0
bne SetR4Zero6
mov r6, #0x1
b ConfuseMoveLoop

ConfuseMoveLoop:
mov r1, #0xB
Add r1, r6, r6
ldr r0, .Attacker6
ldrb r0, [r0]
mov r1, #0xB
add r1, r1, r6
ldr r0, .Attacker6
ldrb r0, [r0]
bl UserDataHWord
mov r0, r2
mov r1, #0x0
bl GetAttackData
cmp r2, #0xA6
beq SetSpAttack
cmp r2, #0x76
beq SetAttack
b Loop6

SetSpAttack:
mov r1, #0x1C
b StatBuffCheck
SetAttack:
mov r1, #0x19

StatBuffCheck:
ldr r0, .Attacker6
ldrb r0, [r0]
bl UserDataByte
cmp r0, #0xC
beq SetR4Zero6
b CheckPP6

Loop6:
add r6, r6, #0x1
cmp r6, #0x4
bgt SetR4Zero6
b ConfuseMoveLoop

CheckPP6:
mov r1, #0x23
add r1, r1, r6
ldr r0, .Attacker6
ldrb r0, [r0]
bl UserDataByte
cmp r2, #0x0
beq SetR4Zero6
b SetR4One6

TypeChecking6:
ldr r0, .Attacker6
ldrb r0, [r0]
mov r1, #0x21
bl UserDataByte
cmp r2, #0xA
beq Loop6
mov r1, #0x22
bl UserDataByte
cmp r2, #0xA
beq Loop6
b CheckPP6

SetR4Zero6:
mov r4, #0x0
b Return6

SetR4One6:
mov r4, #0x1

Return6:
pop {r0-r3, r6, pc}

.align 2
.Attacker6: .word 0x02023D6B

/*Item Checks*/
.align 2

ItemCheck:
push {r0-r3, r6, lr}
ldr r0, .Attacker7
ldrb r0, [r0]
bl GetSide
mov r2, #0x1
and r2, r0
ldr r0, .Status42
lsl r2, r2, #0x1
add r2, r2, r0
ldrh r0, [r2]
mov r1, #0xFF
add r1, r1, #0x1
and r1, r0
cmp r0, #0x0
bne SetR4Zero8
ldr r0, .APartner7
ldrb r0, [r0]
mov r1, #0x2E
bl UserDataHWord
mov r1, #0xFF
add r1, r1, #0x16
cmp r2, r1
beq Sapphire
add r1, r1, #0x2
cmp r2, r1
beq Pearl
add r1, r1, #0x6
cmp r2, r1
beq Ruby
add r1, r1, #0x3E
cmp r2, r1
beq Amethyst
add r1, r1, #0x1
cmp r2, r1
beq Garnet
b SetR4Zero8

Sapphire:
mov r1, #0x1C
b FullStats

Ruby:
mov r1, #0x19
b FullStats

Pearl:
mov r1, #0x1B
b FullStats

Amethyst:
mov r1, #0x1A
b FullStats

Garnet:
mov r1, #0x1D
b FullStats

FullStats:
mov r7, r1
bl UserDataByte
cmp r2, #0xC
beq SetR4Zero8
mov r6, #0x1
b StatLowering

SetR4Zero8:
mov r4, #0x0
b Return10

Return10:
pop {r0-r3, r6, pc}

StatLowering:
mov r1, #0xB
Add r1, r6, r6
ldr r0, .Attacker7
ldrb r0, [r0]
mov r1, #0xB
add r1, r1, r6
ldr r0, .Attacker7
ldrb r0, [r0]
bl UserDataHWord
mov r0, r2
mov r1, #0x0
bl GetAttackData
cmp r7, #0x19
beq NoAttack
cmp r7, #0x1A
beq NoDefense
cmp r7, #0x1B
beq NoSpeed
cmp r7, #0x1C
beq NoSpAttack
cmp r7, #0x1D
beq NoSpDefense

NoAttack:
cmp r2, #0x13
beq CheckPP7
cmp r2, #0x14
beq CheckPP7
cmp r2, #0x15
beq CheckPP7
cmp r2, #0x16
beq CheckPP7
cmp r2, #0x17
beq CheckPP7
cmp r2, #0x3B
beq CheckPP7
cmp r2, #0x3C
beq CheckPP7
cmp r2, #0x3D
beq CheckPP7
cmp r2, #0x3E
beq CheckPP7
b Loop7

NoDefense:
cmp r2, #0x12
beq CheckPP7
cmp r2, #0x14
beq CheckPP7
cmp r2, #0x15
beq CheckPP7
cmp r2, #0x16
beq CheckPP7
cmp r2, #0x17
beq CheckPP7
cmp r2, #0x3A
beq CheckPP7
cmp r2, #0x3C
beq CheckPP7
cmp r2, #0x3D
beq CheckPP7
cmp r2, #0x3E
beq CheckPP7
b Loop7

NoSpeed:
cmp r2, #0x12
beq CheckPP7
cmp r2, #0x13
beq CheckPP7
cmp r2, #0x15
beq CheckPP7
cmp r2, #0x16
beq CheckPP7
cmp r2, #0x17
beq CheckPP7
cmp r2, #0x3A
beq CheckPP7
cmp r2, #0x3B
beq CheckPP7
cmp r2, #0x3D
beq CheckPP7
cmp r2, #0x3E
beq CheckPP7
cmp r2, #0xCD
beq CheckPP7
b Loop7

NoSpAttack:
cmp r2, #0x12
beq CheckPP7
cmp r2, #0x13
beq CheckPP7
cmp r2, #0x14
beq CheckPP7
cmp r2, #0x16
beq CheckPP7
cmp r2, #0x17
beq CheckPP7
cmp r2, #0x3A
beq CheckPP7
cmp r2, #0x3B
beq CheckPP7
cmp r2, #0x3C
beq CheckPP7
cmp r2, #0x3E
beq CheckPP7
cmp r2, #0xCD
beq CheckPP7
b Loop7

NoSpDefense:
cmp r2, #0x12
beq CheckPP7
cmp r2, #0x13
beq CheckPP7
cmp r2, #0x14
beq CheckPP7
cmp r2, #0x15
beq CheckPP7
cmp r2, #0x17
beq CheckPP7
cmp r2, #0x3A
beq CheckPP7
cmp r2, #0x3B
beq CheckPP7
cmp r2, #0x3C
beq CheckPP7
cmp r2, #0x3D
beq CheckPP7
cmp r2, #0xCD
beq CheckPP7
b Loop7

Loop7:
add r6, r6, #0x1
cmp r6, #0x4
bgt SetR4Zero7
b StatLowering

CheckPP7:
mov r1, #0x23
add r1, r1, r6
ldr r0, .Attacker7
ldrb r0, [r0]
bl UserDataByte
cmp r2, #0x0
beq SetR4Zero7
b SetR4One7

SetR4Zero7:
mov r4, #0x0
b Return7

SetR4One7:
mov r4, #0x1

Return7:
pop {r0-r3, r6, pc}

.align 2
.Attacker7: .word 0x2023D6B
.APartner7: .word 0x2023D60
.Status42: .word 0x2023DDE

/*BL Functions*/
.align 2

GetHealth:
/*Target is in r0*/
/*Stores to r3*/
push {r1-r2, lr}
mov r1, #0x28
bl UserDataHWord
mov r3, r2
mov r1, #0x2C
bl UserDataHWord
mov r0, r3
mov r1, r2
mov r2, #0x64
mul r0, r2
bl Divide
mov r3, r0
pop {r1-r2, pc}

UserDataByte:
/*Put the target number in r0*/
/*Put the byte for the thing you want in r1*/
/*Returns to r2*/
push {r0-r1, r3-r4, lr}
mov r4, #0x58
ldr r3, .UserData
mul r0, r4
add r3, r3, r0
add r3, r3, r1
ldrb r2, [r3]
pop {r0-r1, r3-r4, pc}

UserDataHWord:
/*Put the target number in r0*/
/*Put the byte for the thing you want in r1*/
/*Returns to r2*/
push {r0-r1, r3-r4, lr}
mov r4, #0x58
ldr r3, .UserData
mul r0, r4
add r3, r3, r0
add r3, r3, r1
ldrh r2, [r3]
pop {r0-r1, r3-r4, pc}

UserDataWord:
/*Put the target number in r0*/
/*Put the byte for the thing you want in r1*/
/*Returns to r2*/
push {r0-r1, r3-r4, lr}
mov r4, #0x58
ldr r3, .UserData
mul r0, r4
add r3, r3, r0
add r3, r3, r1
ldr r2, [r3]
pop {r0-r1, r3-r4, pc}

GetAttackData:
/*Move is in r0*/
/*Data you want is in r1*/
/*Result in r2*/
push {r0-r1, r3-r4, lr}
ldr r3, .AttackData
mov r4, #0xC
mul r0, r4
add r0, r0, r3
add r0, r0, r1
ldrb r2, [r0]
pop {r0-r1, r3-r4, pc}

Divide:
ldr r3, .Divider
bx r3

GetSide:
ldr r3, .GetSide
bx r3

RNG:
ldr r3, .GetRNG
bx r3

.align 2
.UserData: .word 0x02023BE4
.AttackData: .word 0x08250C04
.Attacker: .word 0x02023D6B
.Defender: .word 0x02023D6C
.APartner: .word 0x02023D60
.DPartner: .word 0x02023D61
.GetRNG: .word 0x08044EC9
.Divider: .word 0x081E4019
.GetSide: .word 0x080751D9


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:
#dynamic 0x740000
#freespacebyte 0xFF

#org @Switch
jumpifcannotswitch 0x81 @NoSwitch
attackanimation
waitanimation
openpartyscreen 0x1 @NoSwitch
cmde2 0x1
waitstate
cmd51 0x1 0x2
cmd58 0x1
switch1 0x1
switch2 0x1
cmd73 0x1
printstring 0x3
switch3 0x1 0x1
waitstate
cmd52 0x1
goto 0x81D694E

#org @NoSwitch
pause 0x20
orbyte 0x2023DCC 0x20
goto 0x81D694E


3. Insert this routine:
Spoiler:
/*Put 00 48 00 47 XX XX XX 08 at 0x15EE0*/
.text
.align 2
.thumb
.thumb_func

Main:
ldr r1, .MoveIndex
ldr r0, .MoveThing
cmp r2, #0x94
beq Switch
strh r3, [r0]
strh r3, [r1]
b Return

Switch:
strh r2, [r0]
strh r2, [r1]

Return:
ldr r0, .Return
bx r0

.align 2
.MoveIndex: .word 0x02023D4A
.MoveThing: .word 0x02023D4C
.Return: .word 0x08015EE9


4. Then insert this routine:
Spoiler:
/*Put 01 49 08 47 00 00 00 00 XX XX XX 08 at 0x148A4*/
/*6F82D4*/
.text
.align 2
.thumb
.thumb_func

Main:
push {r0}
ldr r0, .BattleType
ldr r0, [r0]
mov r1, #0x8
and r0, r1
cmp r0, #0x0
beq PopR0
pop {r0}
cmp r2, #0x0
beq Player
cmp r2, #0x2
beq Player
b AI

PopR0:
pop {r0}

Player:
mov r1, #0x58
mul r1, r2
add r0, r0, r1
add r3, #0xC
add r0, r0, r3
ldrh r0, [r0]
strh r0, [r5]
ldr r3, .Return
bx r3

AI:
push {r0-r4}
mov r4, #0x0

TrainerBattle:
ldr r0, .BattleType
ldr r0, [r0]
mov r1, #0x1 /*Double*/
and r0, r1
cmp r0, #0x0
bne NoSwitch

/*Check If Switching Is Possible*/

HowManyInParty:
ldr r0, .FirstPoke
mov r1, #0x64
mul r1, r4
add r0, r0, r1
mov r1, #0x41
bl Decrypt
cmp r0, #0x0
beq BattleType
mov r1, #0xCE
lsl r1, r1, #0x1
cmp r0, r1
beq BattleType
ldr r0, .FirstPoke
mov r1, #0x64
mul r1, r4
add r0, r0, r1
mov r1, #0x39
bl Decrypt
cmp r0, #0x0
beq BattleType
b Loop

Loop:
add r4, r4, #0x1
cmp r4, #0x5
ble HowManyInParty
b AbilityCheck

BattleType:
ldr r0, .BattleType
ldr r0, [r0]
mov r1, #0x1 /*Double*/
and r0, r1
cmp r0, #0x0
beq Single

Double:
cmp r4, #0x2
ble NoSwitch
b AbilityCheck

Single:
cmp r4, #0x1
ble NoSwitch

AbilityCheck:
mov r0, #0x0
mov r1, #0x20
bl GetUserData
cmp r2, #0x17
beq NoSwitch
cmp r2, #0x47
beq ATCheck
cmp r2, #0x2A
beq MPCheck
mov r0, #0x2
mov r1, #0x20
bl GetUserData
cmp r2, #0x17
beq NoSwitch
cmp r2, #0x47
beq ATCheck
cmp r2, #0x2A
beq MPCheck
b Status

ATCheck:
ldr r0, .Attacker
ldrb r0, [r0]
mov r1, #0x21
bl GetUserData
cmp r2, #0x2
beq Status
mov r1, #0x22
bl GetUserData
cmp r2, #0x2
beq Status
mov r1, #0x20
bl GetUserData
cmp r2, #0x1A
beq Status
b NoSwitch

MPCheck:
ldr r0, .Attacker
ldrb r0, [r0]
mov r1, #0x21
bl GetUserData
cmp r2, #0x8
beq NoSwitch
mov r1, #0x22
bl GetUserData
cmp r2, #0x8
beq NoSwitch
b Status

Status:
ldr r0, .Attacker
ldrb r0, [r0]
bl GetStatus2
ldr r1, .Compare
and r2, r1
cmp r2, #0x0
bne NoSwitch
ldr r2, .Status3
lsl r0, r0, #0x2
add r0, r0, r2
ldr r0, [r0]
mov r0, #0x80
lsl r0, r0, #0x3
and r2, r0
cmp r2, #0x0
bne NoSwitch
mov r1, #0x0

MoveViability:
ldr r0, .MoveScore
ldrb r2, [r0]
cmp r2, #0xFF
beq SetMove

Loop2:
add r1, r1, #0x1
cmp r1, #0x3
bge NoGoodMoves
mov r1, #0x0
mov r3, #0x0
b MoveViability

NoGoodMoves:
ldr r0, .MoveScore
ldrb r2, [r0]
cmp r2, #0x4E
ble SetScore
b Loop3

SetScore:
add r3, r3, #0x1

Loop3:
add r1, r1, #0x1
cmp r1, #0x3
bge CmpScore
b NoGoodMoves

CmpScore:
cmp r3, #0x4
bge SetMove
b NoSwitch

SetMove:
mov r0, #0x94
strh r0, [r5]
pop {r0-r4}
ldr r3, .Return
bx r3

NoSwitch:
pop {r0-r4}
b Player

/*BL Functions*/

GetUserData:
push {r0, r3-r4, lr}
mov r4, #0x58
ldr r3, .UserData
mul r0, r4
add r3, r3, r0
add r3, r3, r1
ldrb r2, [r3]
pop {r0, r3-r4, pc}

GetStatus2:
push {r0, r3-r4, lr}
mov r4, #0x58
ldr r3, .UserData
mul r0, r4
add r3, r3, r0
add r3, r3, #0x50
ldr r2, [r3]
pop {r0, r3-r4, pc}

Decrypt:
ldr r3, .Decrypt
bx r3

.align 2
.Return: .word 0x080148B3
.MoveScore: .word 0x020003A8
.FirstPoke: .word 0x0202402C
.Decrypt: .word 0x0803FBE9
.BattleType: .word 0x02022B4C
.Compare: .word 0x0400E000
.Attacker: .word 0x02023D6B
.Status3: .word 0x02023DFC
.UserData: .word 0x02023BE4


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:
.text
.align 2
.thumb
.thumb_func

Main:
push {r4, lr}
ldr r2, .Resources
ldr r0, [r2]
add r0, r0, #0x14
ldr r0, [r0]
add r1, r0, #0x4 /*This is 020003A5 which is the move number RAM*/
ldrb r0, [r0, #0x1] /*This is the move number*/
add r1, r1, r0

GetValue:
ldr r3, .AICursor
ldr r0, [r3]
ldrb r0, [r0, #0x1]
cmp r0, #0x5E /*Change this to set the Switch Score*/
beq Set255

AddToScore:
ldrb r4, [r1]
cmp r4, #0xFF
beq Return
add r0, r0, r4
strb r0, [r1]

AddToScoreNegativeCheck:
ldr r0, [r2]
add r0, r0, #0x5
ldr r1, [r0]
add r0, r1, #0x4
ldrb r1, [r1, #0x1]
add r1, r0, r1
mov r0, #0x0
ldsb r0, [r1, r0]
cmp r0, #0x0
bge Return
mov r0, #0x0
strb r0, [r1]

Set255:
mov r0, #0xFF
strb r0, [r1]

Return:
ldr r0, [r3]
add r0, r0, #0x2
str r0, [r3]
pop {r4}
pop {r0}
bx r0

.align 2
.Resources: .word 0x02023FF4
.AICursor: .word 0x02039A00


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:
.text
.align 2
.thumb
.thumb_func

Main:
cmp r1, #0xA5

Compare:
pop {r1}
cmp r1, #0xA5
beq Skip
cmp r1, #0x0
beq Skip
cmp r1, r5
beq Skip
cmp r1, #0xA6
beq Skip
cmp r1, #0x94
beq Skip
b Branch

Skip:
ldr r3, .Return
bx r3

Branch:
ldr r0, .Return2
bx r0

.align 2
.Return: .word 0x0802A191
.Return2: .word 0x0802A065


You'll probably also want to do a similar thing to the above with Mimic.
"The human sacrificed himself, to save the Pokemon. I pitted them against each other, but not until they set aside their differences did I see the true power they all share deep inside. I see now that the circumstances of one's birth are irrelevant; it is what you do with the gift of life that determines who you are." -Mewtwo
Male
California
Seen 3 Weeks Ago
Posted July 27th, 2018
82 posts
1.7 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

Age 22
Male
A place :D
Seen 5 Days Ago
Posted July 19th, 2019
361 posts
6.4 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.
"The human sacrificed himself, to save the Pokemon. I pitted them against each other, but not until they set aside their differences did I see the true power they all share deep inside. I see now that the circumstances of one's birth are irrelevant; it is what you do with the gift of life that determines who you are." -Mewtwo
Male
Forina
Seen 13 Hours Ago
Posted July 13th, 2019
110 posts
12.3 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.

AkameTheBulbasaur

Akame Marukawa of Iyotono

Age 22
Male
A place :D
Seen 5 Days Ago
Posted July 19th, 2019
361 posts
6.4 Years
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.
If you mean whether or not APartner and Attacker Partner mean the same thing, then yes they do. APartner is just easier to write.
"The human sacrificed himself, to save the Pokemon. I pitted them against each other, but not until they set aside their differences did I see the true power they all share deep inside. I see now that the circumstances of one's birth are irrelevant; it is what you do with the gift of life that determines who you are." -Mewtwo

Lance Koijer 2.0

Lance Koijer

Male
Criscanto Town
Seen 21 Hours Ago
Posted 1 Day Ago
76 posts
1.5 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?
Male
Forina
Seen 13 Hours Ago
Posted July 13th, 2019
110 posts
12.3 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?

AkameTheBulbasaur

Akame Marukawa of Iyotono

Age 22
Male
A place :D
Seen 5 Days Ago
Posted July 19th, 2019
361 posts
6.4 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.
"The human sacrificed himself, to save the Pokemon. I pitted them against each other, but not until they set aside their differences did I see the true power they all share deep inside. I see now that the circumstances of one's birth are irrelevant; it is what you do with the gift of life that determines who you are." -Mewtwo

AkameTheBulbasaur

Akame Marukawa of Iyotono

Age 22
Male
A place :D
Seen 5 Days Ago
Posted July 19th, 2019
361 posts
6.4 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.
"The human sacrificed himself, to save the Pokemon. I pitted them against each other, but not until they set aside their differences did I see the true power they all share deep inside. I see now that the circumstances of one's birth are irrelevant; it is what you do with the gift of life that determines who you are." -Mewtwo
Male
Forina
Seen 13 Hours Ago
Posted July 13th, 2019
110 posts
12.3 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?

AkameTheBulbasaur

Akame Marukawa of Iyotono

Age 22
Male
A place :D
Seen 5 Days Ago
Posted July 19th, 2019
361 posts
6.4 Years
Okay. Thanks for answering. In the JumpIfDamageBonusEquals AI commands, why Fairy doesn't use the own Fairy move instead of the Metronome?
Because in my game (where I originally developed the routine) Metronome was a Fairy Type move. If it's not for you you'll want to change that to a move that is Fairy Type for you.
"The human sacrificed himself, to save the Pokemon. I pitted them against each other, but not until they set aside their differences did I see the true power they all share deep inside. I see now that the circumstances of one's birth are irrelevant; it is what you do with the gift of life that determines who you are." -Mewtwo

Lance Koijer 2.0

Lance Koijer

Male
Criscanto Town
Seen 21 Hours Ago
Posted 1 Day Ago
76 posts
1.5 Years
Because in my game (where I originally developed the routine) Metronome was a Fairy Type move. If it's not for you you'll want to change that to a move that is Fairy Type for you.

Wow thanks for the response...

anyway.. What I was asking is if it's possible to use AI Script on player not just on aoppomemt trainer...

Another question, would it be possible to make it in wild battles? Not the wildbattle command in XSE but the actual wild battle where you will emcounter Pokémon in grass and water areas and etcetera.

Thanks man.

AkameTheBulbasaur

Akame Marukawa of Iyotono

Age 22
Male
A place :D
Seen 5 Days Ago
Posted July 19th, 2019
361 posts
6.4 Years
Wow thanks for the response...

anyway.. What I was asking is if it's possible to use AI Script on player not just on aoppomemt trainer...

Another question, would it be possible to make it in wild battles? Not the wildbattle command in XSE but the actual wild battle where you will emcounter Pokémon in grass and water areas and etcetera.

Thanks man.
The AI Scripts are only activated for the AI, not any manual input (like the player). It is possible for wild battles, but only in certain circumstances. The last two scripts in the table are used for wild Pokemon battles. One is the script that makes the Pokemon run away in Safari battles, and the other I believe is the one that makes Entei/Raikou/Suicune run away. I don't know how they are activated though, so I'm not sure how to make them work for normal wild battles.
"The human sacrificed himself, to save the Pokemon. I pitted them against each other, but not until they set aside their differences did I see the true power they all share deep inside. I see now that the circumstances of one's birth are irrelevant; it is what you do with the gift of life that determines who you are." -Mewtwo

Lance Koijer 2.0

Lance Koijer

Male
Criscanto Town
Seen 21 Hours Ago
Posted 1 Day Ago
76 posts
1.5 Years
The AI Scripts are only activated for the AI, not any manual input (like the player). It is possible for wild battles, but only in certain circumstances. The last two scripts in the table are used for wild Pokemon battles. One is the script that makes the Pokemon run away in Safari battles, and the other I believe is the one that makes Entei/Raikou/Suicune run away. I don't know how they are activated though, so I'm not sure how to make them work for normal wild battles.
Thanks man. That really is a good finding. Kudos
Advertiser Content