Grant Grass type Pokémon immunity to Powder Spore based moves - pret/pokecrystal GitHub Wiki

Generation 6 introduced an interesting mechanic where Grass-type Pokémon are unaffected by moves based on powder/spores, and there are five moves in Generation 2 which meet this criteria: Cotton Spore, Poison Powder, Sleep Powder, Spore and Stun Spore. In this tutorial, we'll implement this immunity and also teach the AI how to act properly.

Contents

  1. Create the Powder/Spore move list
  2. Create IsInByteArray
  3. Add a new Battle Command
  4. Make the AI Discourage Powder/Spore moves if the opponent is Grass-type

1. Create the Powder/Spore move list

Let's create home/powder_moves.asm:

+PowderMoves::
+	db POISONPOWDER
+	db SLEEP_POWDER
+	db SPORE
+	db COTTON_SPORE
+	db STUN_SPORE
+	db -1

And include it in home.asm:

 SECTION "Home", ROM0
 ...
+INCLUDE "home/powder_moves.asm"

You might be wondering why we're adding this list in ROM0 instead of using another bank, but don't worry, this is going to be explained in further steps. For now let's proceed with the next one.

2. Create IsInByteArray

Go to home/arrays.asm:

+IsInByteArray::
+	ld de, 1
+; fallthrough
 IsInArray::
 ; Find value a for every de bytes in array hl.
 ; Return index in b and carry if found.
	...

As the comments say, the job of IsInArray is to find the value of register a in an array loaded in hl, and you advance de bytes forward. We made this small addition so that we can have a function that always advances one byte forward in a given array, which we'll use later.

3. Add a new Battle Command

Now we need a battle command that checks whether the used move is powder/spore based or not and apply the immunity depending on the opponent's types, but it doesn't exist yet so we'll have create it. First edit macros/scripts/battle_commands.asm:

; BattleCommandPointers indexes (see data/battle/effect_command_pointers.asm)
	const_def 1
	command checkturn               ; 01
	...
	command traptarget              ; 3b
-	command effect0x3c              ; 3c
+	command checkpowder             ; 3c
	command rampage                 ; 3d
	...

We could've also put the command at the end of the list, but for the sake of saving space we're replacing effect0x3c since it's unused. Now let's edit data/battle/effect_command_pointers.asm:

 BattleCommandPointers:
 ; entries correspond to macros/scripts/battle_commands.asm
	table_width 2, BattleCommandPointers
	dw BattleCommand_CheckTurn
	...
	dw BattleCommand_TrapTarget
-	dw BattleCommand_Unused3C
+	dw BattleCommand_CheckPowder
	dw BattleCommand_Rampage
	...

And then edit engine/battle/effect_commands.asm:

+BattleCommand_CheckPowder:
+; Checks if the move is powder/spore-based and 
+; if the opponent is Grass-type
+	ld a, BATTLE_VARS_MOVE_ANIM
+	call GetBattleVar
+	ld hl, PowderMoves
+	call IsInByteArray
+	ret nc
+
+; If the opponent is Grass-type, the move fails.
+	ld hl, wEnemyMonType1
+	ldh a, [hBattleTurn]
+	and a
+	jr z, .CheckGrassType
+	ld hl, wBattleMonType1
+
+.CheckGrassType:
+	ld a, [hli]
+	cp GRASS
+	jr z, .Immune
+	ld a, [hl]
+	cp GRASS
+	ret nz
+	;fallthrough
+.Immune:
+	ld a, 1
+	ld [wAttackMissed], a
+	ret

The comments make this self-explanatory. BattleCommand_CheckPowder first checks if the move is in the PowderMoves list and then checks the opponent's types to determine whether it hits or misses.

Before proceeding, let's explain something important. To access the PowderMoves list it needs to be included either in the same bank as engine/battle/effect_commands.asm (which is named "Effect Commands") or in the ROM0 bank. We decided to opt for the latter because we're going to check this list from another file that is located in another bank, and ROM0 can be accessed by any file located in any bank (also, the list is pretty short, so it won't fill ROM0). There are more practical workarounds, so don't get used to this.

Anyways, now that we've finally created our new battle command, let's add it to some already existing move effects that are used by Powder/Spore moves. Go to data/moves/effects.asm:

MoveEffects: ; used only for BANK(MoveEffects)
 ...
 DoSleep:
	checkobedience
	usedmovetext
	doturn
	checkhit
+	checkpowder
	checksafeguard
	sleeptarget
	endmove
 ...

 SpeedDown2:
	checkobedience
	usedmovetext
	doturn
	checkhit
+	checkpowder
	speeddown2
	lowersub
	statdownanim
	raisesub
	statdownmessage
	statdownfailtext
	endmove
...

 DoPoison:
	checkobedience
	usedmovetext
	doturn
	checkhit
+	checkpowder
	stab
	checksafeguard
	poison
	endmove

 DoParalyze:
	checkobedience
	usedmovetext
	doturn
	stab
	checkhit
+	checkpowder
	checksafeguard
	paralyze
	endmove
...

DoSleep is used by both Sleep Powder and Spore, SpeedDown2 is used by Cotton Spore, DoPoison by Poison Powder, and DoParalyze by Stun Spore.

Now the mechanic is completely functional! But, do you remember that I told you we're gonna use the PowderMoves list in another file? If you paid attention, I said at the start of the tutorial we're going to teach the AI how to discourage these moves depending on the opponent's types. So let's go to the final step!

4. Make the AI discourage Powder/Spore moves if the opponent is Grass-type

Go to engine/battle/ai/scoring.asm and edit AI_Status:

 AI_Status:
 ...
	inc de
	call AIGetEnemyMove

+; Check if the opponent is immune to powder/spore moves.      
+	ld a, [wEnemyMoveStruct + MOVE_ANIM]
+	push bc
+	push de
+	push hl
+	ld hl, PowderMoves
+	call IsInByteArray
+	pop hl
+	pop de
+	pop bc
+	jr nc, .normal_check
+
+	ld a, [wBattleMonType1]
+	cp GRASS
+	jr z, .immune
+	ld a, [wBattleMonType2]
+	cp GRASS
+	jr z, .immune
+
+.normal_check
	ld a, [wEnemyMoveStruct + MOVE_EFFECT]
	cp EFFECT_TOXIC
	jr z, .poisonimmunity
	cp EFFECT_POISON
	jr z, .poisonimmunity
	cp EFFECT_SLEEP
	jr z, .typeimmunity
	cp EFFECT_PARALYZE
	jr z, .typeimmunity

...

Explanation time! (Last one, I swear). The logic behind this is very similar to BattleCommand_CheckPowder, where we store the move in a and do our powder check; if it's not powder/spore based, we proceed with the original check, but if it is then we check the opponent's types. If the final check succeds, we jump to .immune where it discourages the AI from using the move. The pushs and pops are used to preserve bc, de and hl since AI_Status uses the original values to check each of the AI's moves.

And that's it! Now that the mechanic is functional and the AI knows how to use it properly, the tutorial is finally complete!