Add a new AI layer - pret/pokecrystal GitHub Wiki

As explained in this larger AI tutorial, the behavior of computer trainers is broken up into "layers," allowing different layers to be given to different trainer classes without each layer messing up the others. There exist some empty slots that can be filled with new layers, so here's how to do that.

Contents

  1. Add a new AI_ constant
  2. Add a new AI pointer
  3. Write the code for the new layer
  4. Apply the new layer to trainer classes

1. Add a new AI_ constant

In constants/trainer_data_constants.asm:

...

; TRNATTR_AI_MOVE_WEIGHTS bit flags (wEnemyTrainerAIFlags)
; AIScoringPointers indexes (see engine/battle/ai/move.asm)
	const_def
	shift_const AI_BASIC
	shift_const AI_SETUP
	shift_const AI_TYPES
	shift_const AI_OFFENSIVE
	shift_const AI_SMART
	shift_const AI_OPPORTUNIST
	shift_const AI_AGGRESSIVE
	shift_const AI_CAUTIOUS
	shift_const AI_STATUS
	shift_const AI_RISKY
+	shift_const AI_IMMUNITIES
DEF NO_AI EQU 0

...

Name the new constant whatever you want based on what it's going to do.

2. Add a new AI pointer

In engine/battle/ai/move.asm:

...

AIScoringPointers:
; entries correspond to AI_* constants
	dw AI_Basic
	dw AI_Setup
	dw AI_Types
	dw AI_Offensive
	dw AI_Smart
	dw AI_Opportunist
	dw AI_Aggressive
	dw AI_Cautious
	dw AI_Status
	dw AI_Risky
-	dw AI_None
+	dw AI_Immunities
	dw AI_None
	dw AI_None
	dw AI_None
	dw AI_None
	dw AI_None

This will point to a function label in engine/battle/ai/scoring.asm. There are six slots pointing to AI_None, a label which does nothing. Therefore, all six of these slots can potentially be filled with new layers. You don't need to make sure to leave one filled with AI_None; it's not used as a terminator, so it doesn't need to appear here at all.

The order of these pointers needs to be the same as the order of the AI_* constants in constants/trainer_data_constants.asm. Beyond that, however, their order doesn't matter. The layers are executed in this order, but the idea of "layers" is that they can function independently from each other, so they can be run in any order. Of course, if you write bad code, then that might not be the case.

If you look higher up in engine/battle/ai/move.asm...

AIChooseMove:
...

.CheckLayer:
	pop hl
	pop bc

	ld a, c
	cp 16 ; up to 16 scoring layers
	jr z, .DecrementScores

...

...you'll see that the number of layers, 16, is explicitly defined. 16 bit flags require 2 bytes to be stored. If you stroll on over to ram/wram.asm and search for the label wEnemyTrainerAIFlags...

...

wEnemyTrainerItem1:: db
wEnemyTrainerItem2:: db
wEnemyTrainerBaseReward:: db
wEnemyTrainerAIFlags:: ds 3
wOTClassName:: ds TRAINER_CLASS_NAME_LENGTH

...

...you'll see that 3 bytes are actually reserved for it (ds 3). However, the third is used for flags pertaining to the computer using items or switching out Pokémon, so it's not necessarily free to use for more layers. The item and switch flags do only use 6 out of 8 bits, one of which is garbage data, so if you're clever you might be able to squeeze out 3 more layers, but that'll be on you to figure out.

3. Write the code for the new layer

Anywhere in engine/battle/ai/scoring.asm, create a new function with the same name as the pointer from the last step. Then write code for whatever you want that layer to do. A full explanation of how the AI works is outside the scope of this tutorial, so we'll just cover one example.

The existing AI_Types layer does a basic check of the computer Pokémon's moves for type effectiveness. It "dismisses" any move that the player is immune to (meaning the computer will basically never pick that move), and does some lighter "encouragement" and "discouragement" for moves that are super effective or not very effective. However, the AI_Types layer is only used by weaker trainer classes, and gets replaced with AI_Aggressive on stronger ones. AI_Aggressive does a more complex check to see which of the computer's moves will do the most damage to the player, taking into account not only type effectiveness, but base power and so on. The thing is, all that it does with this information is discourage any move that isn't the best one, and if it can't find a best one, it does nothing at all. As a result, since it doesn't also run AI_Types, there's a chance that the computer could end up picking a move that the player is immune to. You've probably never seen this happen during play, which is because other layers are also doing other things, but this is just an example of what can be done with new layers. What we're going to do is break off the immunity check from inside of AI_Types to create a new layer, AI_Immunities, and then run AI_Immunities alongside both AI_Types and AI_Aggressive, thus solving our hypothetical problem.

...

AI_Types:
-; Dismiss any move that the player is immune to.
; Encourage super-effective moves.
; Discourage not very effective moves unless
; all damaging moves are of the same type.

	ld hl, wEnemyAIMoveScores - 1
	ld de, wEnemyMonMoves
	ld b, NUM_MOVES + 1
.checkmove
	dec b
	ret z

	inc hl
	ld a, [de]
	and a
	ret z

	inc de
	call AIGetEnemyMove

	push hl
	push bc
	push de
	ld a, 1
	ldh [hBattleTurn], a
	callfar BattleCheckTypeMatchup
	pop de
	pop bc
	pop hl

	ld a, [wTypeMatchup]
-	and a
-	jr z, .immune
	cp EFFECTIVE
	jr z, .checkmove
	jr c, .noteffective

; effective
	ld a, [wEnemyMoveStruct + MOVE_POWER]
	and a
	jr z, .checkmove
	dec [hl]
	jr .checkmove

.noteffective
; Discourage this move if there are any moves
; that do damage of a different type.
	push hl
	push de
	push bc
	ld a, [wEnemyMoveStruct + MOVE_TYPE]
	ld d, a
	ld hl, wEnemyMonMoves
	ld b, NUM_MOVES + 1
	ld c, 0
.checkmove2
	dec b
	jr z, .movesdone

	ld a, [hli]
	and a
	jr z, .movesdone

	call AIGetEnemyMove
	ld a, [wEnemyMoveStruct + MOVE_TYPE]
	cp d
	jr z, .checkmove2
	ld a, [wEnemyMoveStruct + MOVE_POWER]
	and a
	jr nz, .damaging
	jr .checkmove2

.damaging
	ld c, a
.movesdone
	ld a, c
	pop bc
	pop de
	pop hl
	and a
	jr z, .checkmove
	inc [hl]
	jr .checkmove

-.immune
-	call AIDiscourageMove
-	jr .checkmove
+
+AI_Immunities:
+; Dismiss any move that the player is immune to.
+
+	ld hl, wEnemyAIMoveScores - 1
+	ld de, wEnemyMonMoves
+	ld b, NUM_MOVES + 1
+.checkmove
+	dec b
+	ret z
+
+	inc hl
+	ld a, [de]
+	and a
+	ret z
+
+	inc de
+	call AIGetEnemyMove
+	ld a, [wEnemyMoveStruct + MOVE_POWER]
+	and a
+	jr z, .checkmove
+
+	push hl
+	push bc
+	push de
+	ld a, 1
+	ldh [hBattleTurn], a
+	callfar BattleCheckTypeMatchup
+	pop de
+	pop bc
+	pop hl
+
+	ld a, [wTypeMatchup]
+	and a
+	jr z, .immune
+	jr .checkmove
+
+.immune
+	call AIDiscourageMove
+	jr .checkmove

...

I've added a check so that this new layer only dismisses damaging moves, because there are a lot of cases where a non-damaging move ignores type immunities. AI_Types does do a check for non-damaging moves, but not until after checking for immunities. This is probably because AI_Types is meant to create a less intelligent trainer, but since we're going to use AI_Immunities together with AI_Aggressive as well as AI_Types, we'll err on the smarter side. Some other layers contain handling for non-damaging type immunities, so it should work out.

You may notice that this particular approach has caused us to repeat a lot of code. The pointer table limits us to 16 layers, but the true limiting factor is that engine/battle/ai/scoring.asm needs to fit within a single bank. There are many ways to optimize its contents, and/or it can be split across multiple banks, but such factors are outside the scope of this tutorial, as it is possible to fit in some new code without overflowing.

4. Apply the new layer to trainer classes

Now that we've created a new layer, we need to give it to whichever trainer classes whom we want to have it. This is done in data/trainers/attributes.asm. In our example, we've taken the immunity check out of AI_TYPES, so we'll need to add AI_IMMUNITIES to any trainer class that has AI_TYPES in order to restore their proper behavior, and then we'll also add AI_IMMUNITIES to any class that has AI_AGGRESSIVE, since that was what we set out to do.

TrainerClassAttributes:
; entries correspond to trainer classes (see constants/trainer_constants.asm)
	table_width NUM_TRAINER_ATTRIBUTES

; Falkner
	db NO_ITEM, NO_ITEM ; items
	db 25 ; base reward
-	dw AI_BASIC | AI_SETUP | AI_SMART | AI_AGGRESSIVE | AI_CAUTIOUS | AI_STATUS | AI_RISKY
+	dw AI_BASIC | AI_SETUP | AI_SMART | AI_AGGRESSIVE | AI_CAUTIOUS | AI_STATUS | AI_RISKY | AI_IMMUNITIES
	dw CONTEXT_USE | SWITCH_SOMETIMES

; Whitney
	db NO_ITEM, NO_ITEM ; items
	db 25 ; base reward
-	dw AI_BASIC | AI_SETUP | AI_SMART | AI_AGGRESSIVE | AI_CAUTIOUS | AI_STATUS | AI_RISKY
+	dw AI_BASIC | AI_SETUP | AI_SMART | AI_AGGRESSIVE | AI_CAUTIOUS | AI_STATUS | AI_RISKY | AI_IMMUNITIES
	dw CONTEXT_USE | SWITCH_SOMETIMES

; Bugsy
	db NO_ITEM, NO_ITEM ; items
	db 25 ; base reward
-	dw AI_BASIC | AI_SETUP | AI_SMART | AI_AGGRESSIVE | AI_CAUTIOUS | AI_STATUS | AI_RISKY
+	dw AI_BASIC | AI_SETUP | AI_SMART | AI_AGGRESSIVE | AI_CAUTIOUS | AI_STATUS | AI_RISKY | AI_IMMUNITIES
	dw CONTEXT_USE | SWITCH_SOMETIMES

; Morty
	db NO_ITEM, NO_ITEM ; items
	db 25 ; base reward
-	dw AI_BASIC | AI_SETUP | AI_SMART | AI_AGGRESSIVE | AI_CAUTIOUS | AI_STATUS | AI_RISKY
+	dw AI_BASIC | AI_SETUP | AI_SMART | AI_AGGRESSIVE | AI_CAUTIOUS | AI_STATUS | AI_RISKY | AI_IMMUNITIES
	dw CONTEXT_USE | SWITCH_SOMETIMES

; Pryce
	db HYPER_POTION, NO_ITEM ; items
	db 25 ; base reward
-	dw AI_BASIC | AI_SETUP | AI_SMART | AI_AGGRESSIVE | AI_CAUTIOUS | AI_STATUS | AI_RISKY
+	dw AI_BASIC | AI_SETUP | AI_SMART | AI_AGGRESSIVE | AI_CAUTIOUS | AI_STATUS | AI_RISKY | AI_IMMUNITIES
	dw CONTEXT_USE | SWITCH_SOMETIMES

; Jasmine
	db HYPER_POTION, NO_ITEM ; items
	db 25 ; base reward
-	dw AI_BASIC | AI_SETUP | AI_SMART | AI_AGGRESSIVE | AI_CAUTIOUS | AI_STATUS | AI_RISKY
+	dw AI_BASIC | AI_SETUP | AI_SMART | AI_AGGRESSIVE | AI_CAUTIOUS | AI_STATUS | AI_RISKY | AI_IMMUNITIES
	dw CONTEXT_USE | SWITCH_SOMETIMES

; Chuck
	db FULL_HEAL, NO_ITEM ; items
	db 25 ; base reward
-	dw AI_BASIC | AI_SETUP | AI_SMART | AI_AGGRESSIVE | AI_CAUTIOUS | AI_STATUS | AI_RISKY
+	dw AI_BASIC | AI_SETUP | AI_SMART | AI_AGGRESSIVE | AI_CAUTIOUS | AI_STATUS | AI_RISKY | AI_IMMUNITIES
	dw CONTEXT_USE | SWITCH_SOMETIMES

; Clair
	db FULL_HEAL, HYPER_POTION ; items
	db 25 ; base reward
-	dw AI_BASIC | AI_SETUP | AI_SMART | AI_AGGRESSIVE | AI_CAUTIOUS | AI_STATUS | AI_RISKY
+	dw AI_BASIC | AI_SETUP | AI_SMART | AI_AGGRESSIVE | AI_CAUTIOUS | AI_STATUS | AI_RISKY | AI_IMMUNITIES
	dw CONTEXT_USE | SWITCH_SOMETIMES

; Rival1
	db NO_ITEM, NO_ITEM ; items
	db 15 ; base reward
-	dw AI_BASIC | AI_SETUP | AI_SMART | AI_AGGRESSIVE | AI_CAUTIOUS | AI_STATUS | AI_RISKY
+	dw AI_BASIC | AI_SETUP | AI_SMART | AI_AGGRESSIVE | AI_CAUTIOUS | AI_STATUS | AI_RISKY | AI_IMMUNITIES
	dw CONTEXT_USE | SWITCH_SOMETIMES

; Pokemon Prof
	db NO_ITEM, NO_ITEM ; items
	db 25 ; base reward
-	dw AI_BASIC | AI_AGGRESSIVE | AI_STATUS
+	dw AI_BASIC | AI_AGGRESSIVE | AI_STATUS | AI_IMMUNITIES
	dw CONTEXT_USE | SWITCH_SOMETIMES

; Will
	db MAX_POTION, NO_ITEM ; items
	db 25 ; base reward
-	dw AI_BASIC | AI_SETUP | AI_SMART | AI_AGGRESSIVE | AI_CAUTIOUS | AI_STATUS | AI_RISKY
+	dw AI_BASIC | AI_SETUP | AI_SMART | AI_AGGRESSIVE | AI_CAUTIOUS | AI_STATUS | AI_RISKY | AI_IMMUNITIES
	dw CONTEXT_USE | SWITCH_SOMETIMES

; Cal
	db NO_ITEM, NO_ITEM ; items
	db 25 ; base reward
-	dw AI_BASIC | AI_SETUP | AI_SMART | AI_AGGRESSIVE | AI_CAUTIOUS | AI_STATUS | AI_RISKY
+	dw AI_BASIC | AI_SETUP | AI_SMART | AI_AGGRESSIVE | AI_CAUTIOUS | AI_STATUS | AI_RISKY | AI_IMMUNITIES
	dw CONTEXT_USE | SWITCH_SOMETIMES

; Bruno
	db MAX_POTION, NO_ITEM ; items
	db 25 ; base reward
-	dw AI_BASIC | AI_SETUP | AI_SMART | AI_AGGRESSIVE | AI_CAUTIOUS | AI_STATUS | AI_RISKY
+	dw AI_BASIC | AI_SETUP | AI_SMART | AI_AGGRESSIVE | AI_CAUTIOUS | AI_STATUS | AI_RISKY | AI_IMMUNITIES
	dw CONTEXT_USE | SWITCH_SOMETIMES

; Karen
	db FULL_HEAL, MAX_POTION ; items
	db 25 ; base reward
-	dw AI_BASIC | AI_SETUP | AI_SMART | AI_AGGRESSIVE | AI_CAUTIOUS | AI_STATUS | AI_RISKY
+	dw AI_BASIC | AI_SETUP | AI_SMART | AI_AGGRESSIVE | AI_CAUTIOUS | AI_STATUS | AI_RISKY | AI_IMMUNITIES
	dw CONTEXT_USE | SWITCH_SOMETIMES

; Koga
	db FULL_HEAL, FULL_RESTORE ; items
	db 25 ; base reward
-	dw AI_BASIC | AI_SETUP | AI_SMART | AI_AGGRESSIVE | AI_CAUTIOUS | AI_STATUS | AI_RISKY
+	dw AI_BASIC | AI_SETUP | AI_SMART | AI_AGGRESSIVE | AI_CAUTIOUS | AI_STATUS | AI_RISKY | AI_IMMUNITIES
	dw CONTEXT_USE | SWITCH_SOMETIMES

; Champion
	db FULL_HEAL, FULL_RESTORE ; items
	db 25 ; base reward
-	dw AI_BASIC | AI_SETUP | AI_SMART | AI_AGGRESSIVE | AI_CAUTIOUS | AI_STATUS | AI_RISKY
+	dw AI_BASIC | AI_SETUP | AI_SMART | AI_AGGRESSIVE | AI_CAUTIOUS | AI_STATUS | AI_RISKY | AI_IMMUNITIES
	dw CONTEXT_USE | SWITCH_SOMETIMES

; Brock
	db HYPER_POTION, NO_ITEM ; items
	db 25 ; base reward
-	dw AI_BASIC | AI_SETUP | AI_SMART | AI_AGGRESSIVE | AI_CAUTIOUS | AI_STATUS | AI_RISKY
+	dw AI_BASIC | AI_SETUP | AI_SMART | AI_AGGRESSIVE | AI_CAUTIOUS | AI_STATUS | AI_RISKY | AI_IMMUNITIES
	dw CONTEXT_USE | SWITCH_SOMETIMES

; Misty
	db FULL_HEAL, NO_ITEM ; items
	db 25 ; base reward
-	dw AI_BASIC | AI_SETUP | AI_SMART | AI_AGGRESSIVE | AI_CAUTIOUS | AI_STATUS | AI_RISKY
+	dw AI_BASIC | AI_SETUP | AI_SMART | AI_AGGRESSIVE | AI_CAUTIOUS | AI_STATUS | AI_RISKY | AI_IMMUNITIES
	dw CONTEXT_USE | SWITCH_SOMETIMES

; Lt Surge
	db HYPER_POTION, NO_ITEM ; items
	db 25 ; base reward
-	dw AI_BASIC | AI_SETUP | AI_SMART | AI_AGGRESSIVE | AI_CAUTIOUS | AI_STATUS | AI_RISKY
+	dw AI_BASIC | AI_SETUP | AI_SMART | AI_AGGRESSIVE | AI_CAUTIOUS | AI_STATUS | AI_RISKY | AI_IMMUNITIES
	dw CONTEXT_USE | SWITCH_SOMETIMES

; Scientist
	db NO_ITEM, NO_ITEM ; items
	db 25 ; base reward
-	dw AI_BASIC | AI_SETUP | AI_TYPES | AI_STATUS | AI_RISKY
+	dw AI_BASIC | AI_SETUP | AI_TYPES | AI_STATUS | AI_RISKY | AI_IMMUNITIES
	dw CONTEXT_USE | SWITCH_SOMETIMES

; Erika
	db HYPER_POTION, NO_ITEM ; items
	db 25 ; base reward
-	dw AI_BASIC | AI_SETUP | AI_SMART | AI_AGGRESSIVE | AI_CAUTIOUS | AI_STATUS | AI_RISKY
+	dw AI_BASIC | AI_SETUP | AI_SMART | AI_AGGRESSIVE | AI_CAUTIOUS | AI_STATUS | AI_RISKY | AI_IMMUNITIES
	dw CONTEXT_USE | SWITCH_SOMETIMES

...

; Schoolboy
	db NO_ITEM, NO_ITEM ; items
	db 8 ; base reward
-	dw AI_BASIC | AI_SETUP | AI_TYPES | AI_OPPORTUNIST | AI_CAUTIOUS | AI_STATUS
+	dw AI_BASIC | AI_SETUP | AI_TYPES | AI_OPPORTUNIST | AI_CAUTIOUS | AI_STATUS | AI_IMMUNITIES
	dw CONTEXT_USE | SWITCH_OFTEN

; Bird Keeper
	db NO_ITEM, NO_ITEM ; items
	db 6 ; base reward
-	dw AI_BASIC | AI_TYPES | AI_OFFENSIVE | AI_OPPORTUNIST | AI_STATUS
+	dw AI_BASIC | AI_TYPES | AI_OFFENSIVE | AI_OPPORTUNIST | AI_STATUS | AI_IMMUNITIES
	dw CONTEXT_USE | SWITCH_SOMETIMES

...

; Janine
	db DIRE_HIT, NO_ITEM ; items
	db 25 ; base reward
-	dw AI_BASIC | AI_SETUP | AI_SMART | AI_AGGRESSIVE | AI_CAUTIOUS | AI_STATUS | AI_RISKY
+	dw AI_BASIC | AI_SETUP | AI_SMART | AI_AGGRESSIVE | AI_CAUTIOUS | AI_STATUS | AI_RISKY | AI_IMMUNITIES
	dw CONTEXT_USE | SWITCH_SOMETIMES

; Cooltrainerm
	db NO_ITEM, NO_ITEM ; items
	db 12 ; base reward
-	dw AI_BASIC | AI_SETUP | AI_SMART | AI_AGGRESSIVE | AI_CAUTIOUS | AI_STATUS | AI_RISKY
+	dw AI_BASIC | AI_SETUP | AI_SMART | AI_AGGRESSIVE | AI_CAUTIOUS | AI_STATUS | AI_RISKY | AI_IMMUNITIES
	dw CONTEXT_USE | SWITCH_SOMETIMES

; Cooltrainerf
	db NO_ITEM, NO_ITEM ; items
	db 12 ; base reward
-	dw AI_BASIC | AI_SETUP | AI_SMART | AI_AGGRESSIVE | AI_CAUTIOUS | AI_STATUS | AI_RISKY
+	dw AI_BASIC | AI_SETUP | AI_SMART | AI_AGGRESSIVE | AI_CAUTIOUS | AI_STATUS | AI_RISKY | AI_IMMUNITIES
	dw CONTEXT_USE | SWITCH_SOMETIMES

; Beauty
	db NO_ITEM, NO_ITEM ; items
	db 22 ; base reward
-	dw AI_BASIC | AI_TYPES | AI_OPPORTUNIST | AI_CAUTIOUS | AI_STATUS
+	dw AI_BASIC | AI_TYPES | AI_OPPORTUNIST | AI_CAUTIOUS | AI_STATUS | AI_IMMUNITIES
	dw CONTEXT_USE | SWITCH_SOMETIMES

; Pokemaniac
	db NO_ITEM, NO_ITEM ; items
	db 15 ; base reward
-	dw AI_BASIC | AI_SETUP | AI_OFFENSIVE | AI_AGGRESSIVE | AI_STATUS
+	dw AI_BASIC | AI_SETUP | AI_OFFENSIVE | AI_AGGRESSIVE | AI_STATUS | AI_IMMUNITIES
	dw CONTEXT_USE | SWITCH_SOMETIMES

; Gruntm
	db NO_ITEM, NO_ITEM ; items
	db 10 ; base reward
-	dw AI_BASIC | AI_SETUP | AI_TYPES | AI_OPPORTUNIST | AI_CAUTIOUS | AI_STATUS | AI_RISKY
+	dw AI_BASIC | AI_SETUP | AI_TYPES | AI_OPPORTUNIST | AI_CAUTIOUS | AI_STATUS | AI_RISKY | AI_IMMUNITIES
	dw CONTEXT_USE | SWITCH_SOMETIMES

; Gentleman
	db NO_ITEM, NO_ITEM ; items
	db 18 ; base reward
-	dw AI_BASIC | AI_SETUP | AI_AGGRESSIVE | AI_STATUS
+	dw AI_BASIC | AI_SETUP | AI_AGGRESSIVE | AI_STATUS | AI_IMMUNITIES
	dw CONTEXT_USE | SWITCH_SOMETIMES

; Skier
	db NO_ITEM, NO_ITEM ; items
	db 18 ; base reward
-	dw AI_BASIC | AI_SETUP | AI_TYPES | AI_STATUS | AI_RISKY
+	dw AI_BASIC | AI_SETUP | AI_TYPES | AI_STATUS | AI_RISKY | AI_IMMUNITIES
	dw CONTEXT_USE | SWITCH_SOMETIMES

; Teacher
	db NO_ITEM, NO_ITEM ; items
	db 18 ; base reward
-	dw AI_BASIC | AI_OPPORTUNIST | AI_AGGRESSIVE | AI_STATUS
+	dw AI_BASIC | AI_OPPORTUNIST | AI_AGGRESSIVE | AI_STATUS | AI_IMMUNITIES
	dw CONTEXT_USE | SWITCH_SOMETIMES

; Sabrina
	db HYPER_POTION, NO_ITEM ; items
	db 25 ; base reward
-	dw AI_BASIC | AI_SETUP | AI_SMART | AI_AGGRESSIVE | AI_CAUTIOUS | AI_STATUS | AI_RISKY
+	dw AI_BASIC | AI_SETUP | AI_SMART | AI_AGGRESSIVE | AI_CAUTIOUS | AI_STATUS | AI_RISKY | AI_IMMUNITIES
	dw CONTEXT_USE | SWITCH_SOMETIMES

...

; Fisher
	db NO_ITEM, NO_ITEM ; items
	db 10 ; base reward
-	dw AI_BASIC | AI_TYPES | AI_OPPORTUNIST | AI_CAUTIOUS | AI_STATUS
+	dw AI_BASIC | AI_TYPES | AI_OPPORTUNIST | AI_CAUTIOUS | AI_STATUS | AI_IMMUNITIES
	dw CONTEXT_USE | SWITCH_OFTEN

; Swimmerm
	db NO_ITEM, NO_ITEM ; items
	db 2 ; base reward
-	dw AI_BASIC | AI_SETUP | AI_TYPES | AI_OFFENSIVE | AI_STATUS
+	dw AI_BASIC | AI_SETUP | AI_TYPES | AI_OFFENSIVE | AI_STATUS | AI_IMMUNITIES
	dw CONTEXT_USE | SWITCH_SOMETIMES

; Swimmerf
	db NO_ITEM, NO_ITEM ; items
	db 5 ; base reward
-	dw AI_BASIC | AI_SETUP | AI_TYPES | AI_CAUTIOUS | AI_STATUS
+	dw AI_BASIC | AI_SETUP | AI_TYPES | AI_CAUTIOUS | AI_STATUS | AI_IMMUNITIES
	dw CONTEXT_USE | SWITCH_SOMETIMES

...

; Super Nerd
	db NO_ITEM, NO_ITEM ; items
	db 8 ; base reward
-	dw AI_BASIC | AI_TYPES | AI_SMART | AI_STATUS
+	dw AI_BASIC | AI_TYPES | AI_SMART | AI_STATUS | AI_IMMUNITIES
	dw CONTEXT_USE | SWITCH_SOMETIMES

; Rival2
	db NO_ITEM, NO_ITEM ; items
	db 25 ; base reward
-	dw AI_BASIC | AI_SETUP | AI_SMART | AI_AGGRESSIVE | AI_CAUTIOUS | AI_STATUS | AI_RISKY
+	dw AI_BASIC | AI_SETUP | AI_SMART | AI_AGGRESSIVE | AI_CAUTIOUS | AI_STATUS | AI_RISKY | AI_IMMUNITIES
	dw CONTEXT_USE | SWITCH_SOMETIMES

; Guitarist
	db NO_ITEM, NO_ITEM ; items
	db 8 ; base reward
-	dw AI_BASIC | AI_SETUP | AI_TYPES | AI_CAUTIOUS | AI_STATUS
+	dw AI_BASIC | AI_SETUP | AI_TYPES | AI_CAUTIOUS | AI_STATUS | AI_IMMUNITIES
	dw CONTEXT_USE | SWITCH_SOMETIMES

...

; Biker
	db NO_ITEM, NO_ITEM ; items
	db 8 ; base reward
-	dw AI_BASIC | AI_TYPES | AI_STATUS | AI_RISKY
+	dw AI_BASIC | AI_TYPES | AI_STATUS | AI_RISKY | AI_IMMUNITIES
	dw CONTEXT_USE | SWITCH_SOMETIMES

; Blaine
	db MAX_POTION, FULL_HEAL ; items
	db 25 ; base reward
-	dw AI_BASIC | AI_SETUP | AI_SMART | AI_AGGRESSIVE | AI_CAUTIOUS | AI_STATUS | AI_RISKY
+	dw AI_BASIC | AI_SETUP | AI_SMART | AI_AGGRESSIVE | AI_CAUTIOUS | AI_STATUS | AI_RISKY | AI_IMMUNITIES
	dw CONTEXT_USE | SWITCH_SOMETIMES

...

; Juggler
	db NO_ITEM, NO_ITEM ; items
	db 10 ; base reward
-	dw AI_BASIC | AI_TYPES | AI_SMART | AI_STATUS
+	dw AI_BASIC | AI_TYPES | AI_SMART | AI_STATUS | AI_IMMUNITIES
	dw CONTEXT_USE | SWITCH_SOMETIMES

...

; Executivem
	db NO_ITEM, NO_ITEM ; items
	db 18 ; base reward
-	dw AI_BASIC | AI_SETUP | AI_TYPES | AI_SMART | AI_OPPORTUNIST | AI_CAUTIOUS | AI_STATUS | AI_RISKY
+	dw AI_BASIC | AI_SETUP | AI_TYPES | AI_SMART | AI_OPPORTUNIST | AI_CAUTIOUS | AI_STATUS | AI_RISKY | AI_IMMUNITIES
	dw CONTEXT_USE | SWITCH_SOMETIMES

; Psychic T
	db NO_ITEM, NO_ITEM ; items
	db 8 ; base reward
-	dw AI_BASIC | AI_TYPES | AI_OPPORTUNIST | AI_CAUTIOUS | AI_STATUS
+	dw AI_BASIC | AI_TYPES | AI_OPPORTUNIST | AI_CAUTIOUS | AI_STATUS | AI_IMMUNITIES
	dw CONTEXT_USE | SWITCH_SOMETIMES

...

; Executivef
	db NO_ITEM, NO_ITEM ; items
	db 18 ; base reward
-	dw AI_BASIC | AI_SETUP | AI_TYPES | AI_SMART | AI_OPPORTUNIST | AI_CAUTIOUS | AI_STATUS | AI_RISKY
+	dw AI_BASIC | AI_SETUP | AI_TYPES | AI_SMART | AI_OPPORTUNIST | AI_CAUTIOUS | AI_STATUS | AI_RISKY | AI_IMMUNITIES
	dw CONTEXT_USE | SWITCH_SOMETIMES

; Sage
	db NO_ITEM, NO_ITEM ; items
	db 8 ; base reward
-	dw AI_BASIC | AI_SETUP | AI_TYPES | AI_CAUTIOUS | AI_STATUS | AI_RISKY
+	dw AI_BASIC | AI_SETUP | AI_TYPES | AI_CAUTIOUS | AI_STATUS | AI_RISKY | AI_IMMUNITIES
	dw CONTEXT_USE | SWITCH_SOMETIMES

; Medium
	db NO_ITEM, NO_ITEM ; items
	db 10 ; base reward
-	dw AI_BASIC | AI_SETUP | AI_TYPES | AI_CAUTIOUS | AI_STATUS | AI_RISKY
+	dw AI_BASIC | AI_SETUP | AI_TYPES | AI_CAUTIOUS | AI_STATUS | AI_RISKY | AI_IMMUNITIES
	dw CONTEXT_USE | SWITCH_SOMETIMES

; Boarder
	db NO_ITEM, NO_ITEM ; items
	db 18 ; base reward
-	dw AI_BASIC | AI_TYPES | AI_OPPORTUNIST | AI_STATUS
+	dw AI_BASIC | AI_TYPES | AI_OPPORTUNIST | AI_STATUS | AI_IMMUNITIES
	dw CONTEXT_USE | SWITCH_SOMETIMES

; Pokefanm
	db NO_ITEM, NO_ITEM ; items
	db 20 ; base reward
-	dw AI_BASIC | AI_TYPES | AI_SMART | AI_STATUS
+	dw AI_BASIC | AI_TYPES | AI_SMART | AI_STATUS | AI_IMMUNITIES
	dw CONTEXT_USE | SWITCH_SOMETIMES

; Kimono Girl
	db NO_ITEM, NO_ITEM ; items
	db 18 ; base reward
-	dw AI_BASIC | AI_TYPES | AI_OPPORTUNIST | AI_CAUTIOUS | AI_STATUS
+	dw AI_BASIC | AI_TYPES | AI_OPPORTUNIST | AI_CAUTIOUS | AI_STATUS | AI_IMMUNITIES
	dw CONTEXT_USE | SWITCH_SOMETIMES

...

; Pokefanf
	db NO_ITEM, NO_ITEM ; items
	db 20 ; base reward
-	dw AI_BASIC | AI_TYPES | AI_SMART | AI_STATUS
+	dw AI_BASIC | AI_TYPES | AI_SMART | AI_STATUS | AI_IMMUNITIES
	dw CONTEXT_USE | SWITCH_SOMETIMES

; Red
	db FULL_RESTORE, FULL_RESTORE ; items
	db 25 ; base reward
-	dw AI_BASIC | AI_SETUP | AI_SMART | AI_AGGRESSIVE | AI_CAUTIOUS | AI_STATUS | AI_RISKY
+	dw AI_BASIC | AI_SETUP | AI_SMART | AI_AGGRESSIVE | AI_CAUTIOUS | AI_STATUS | AI_RISKY | AI_IMMUNITIES
	dw CONTEXT_USE | SWITCH_SOMETIMES

; Blue
	db FULL_RESTORE, FULL_RESTORE ; items
	db 25 ; base reward
-	dw AI_BASIC | AI_SETUP | AI_SMART | AI_AGGRESSIVE | AI_CAUTIOUS | AI_STATUS | AI_RISKY
+	dw AI_BASIC | AI_SETUP | AI_SMART | AI_AGGRESSIVE | AI_CAUTIOUS | AI_STATUS | AI_RISKY | AI_IMMUNITIES
	dw CONTEXT_USE | SWITCH_SOMETIMES

; Officer
	db NO_ITEM, NO_ITEM ; items
	db 10 ; base reward
-	dw AI_BASIC | AI_TYPES | AI_OPPORTUNIST | AI_STATUS
+	dw AI_BASIC | AI_TYPES | AI_OPPORTUNIST | AI_STATUS | AI_IMMUNITIES
	dw CONTEXT_USE | SWITCH_SOMETIMES

; Gruntf
	db NO_ITEM, NO_ITEM ; items
	db 10 ; base reward
-	dw AI_BASIC | AI_TYPES | AI_OPPORTUNIST | AI_CAUTIOUS | AI_STATUS | AI_RISKY
+	dw AI_BASIC | AI_TYPES | AI_OPPORTUNIST | AI_CAUTIOUS | AI_STATUS | AI_RISKY | AI_IMMUNITIES
	dw CONTEXT_USE | SWITCH_SOMETIMES

; Mysticalman
	db NO_ITEM, NO_ITEM ; items
	db 25 ; base reward
-	dw AI_BASIC | AI_SETUP | AI_SMART | AI_AGGRESSIVE | AI_CAUTIOUS | AI_STATUS | AI_RISKY
+	dw AI_BASIC | AI_SETUP | AI_SMART | AI_AGGRESSIVE | AI_CAUTIOUS | AI_STATUS | AI_RISKY | AI_IMMUNITIES
	dw CONTEXT_USE | SWITCH_SOMETIMES

	assert_table_length NUM_TRAINER_CLASSES

It would also be possible to give AI_IMMUNITIES to a class that has neither AI_TYPES nor AI_AGGRESSIVE, creating a behavior type that's aware of immunities but otherwise ignorant of type matchups. That's the fun of layers: they can be mixed and matched for new results.

Since these constants are setting bit flags, the order they're listed in here doesn't actually matter; we're just maintaining the same order for the sake of organization. And also since they're setting bit flags, we're not adding any data space when we give trainer classes more layers than they had before, so we don't have to worry about this file overflowing a bank.

That's the gist of adding a new layer, but obviously you can do just about anything with that layer. You may have seen here that "boss" trainers all tend to have the same set of layers, which when combined will more or less result in the smartest behavior. But it would also be possible to create a layer that only gets used by one trainer class, which would be a way to make that class stand out, and thus differentiate special trainers not only from the masses but also from each other. In this way, you could craft a very specific strategy based on one trainer's party, and use a unique layer to make sure that they pull off that strategy properly. For more ideas, you can always check out any open-source hacks that you come across!