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
- Create the Powder/Spore move list
- Create
IsInByteArray
- Add a new Battle Command
- 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.
IsInByteArray
2. Create 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 push
s and pop
s 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!