Prevent Steel‐types from being poisoned by Twineedle - pret/pokecrystal GitHub Wiki

Steel is immune to Poison, and therefore to the poisoned status. However, in Gen 2, there was one non-Poison-type move that could inflict poisoning: the Bug-type Twineedle. Since Steel isn't immune to Bug, this move can poison Steel-types. This was fixed in Gen 3, so it was clearly an oversight. Let's fix it in Gen 2. This will also fix the similar issue of Tri Attack being able to burn Fire-types and freeze Ice-types.

If we look in engine/battle/effect_commands.asm, we can see that there are two functions specifically made to handle status immunity: CheckMoveTypeMatchesTarget and CheckIfTargetIsPoisonType. Their names are self-explanatory, but neither one would quite do what we're looking for. Now, it might be tempting to copy CheckIfTargetIsPoisonType to create a new function that instead checks for the Steel type, which would work. But piling on redundant functions like that will eventually get you in trouble with the memory banks, not to mention we'd have to make two more of them to account for Tri Attack. Luckily, there's a fairly easy way to pull this off while making the code smaller instead of bigger.

First, simply delete CheckMoveTypeMatchesTarget entirely. It was kind of a weirdly specific way to do status immunities, and we won't need it anymore after we're done.

-CheckMoveTypeMatchesTarget:
-; Compare move type to opponent type.
-; Return z if matching the opponent type,
-; unless the move is Normal (Tri Attack).
-
-	push hl
-
-	ld hl, wEnemyMonType1
-	ldh a, [hBattleTurn]
-	and a
-	jr z, .ok
-	ld hl, wBattleMonType1
-.ok
-
-	ld a, BATTLE_VARS_MOVE_TYPE
-	call GetBattleVar
-	cp NORMAL
-	jr z, .normal
-
-	cp [hl]
-	jr z, .return
-
-	inc hl
-	cp [hl]
-
-.return
-	pop hl
-	ret
-
-.normal
-	ld a, 1
-	and a
-	pop hl
-	ret

Next, we're going to modify CheckIfTargetIsPoisonType so that it can check for any type we want, instead of just Poison. As such, we'll rename it CheckIfTargetIsGivenType.

-CheckIfTargetIsPoisonType:
+CheckIfTargetIsGivenType:
+	ld b, a
	ld de, wEnemyMonType1
	ldh a, [hBattleTurn]
	and a
	jr z, .ok
	ld de, wBattleMonType1
.ok
	ld a, [de]
	inc de
-	cp POISON
+	cp b
	ret z
	ld a, [de]
-	cp POISON
+	cp b
	ret

Now instead of cp POISON, the function uses cp b, and since it now starts by loading a into b, it will now check the target's type against whatever type we load into a. Thus, we can check for any type that we specify instead of just for Poison. In order for it to work, though, we'll need to make sure to load the appropriate type into a before calling the function.

So let's go fix all those function calls. We start with BattleCommand_PoisonTarget and BattleCommand_Poison:

BattleCommand_PoisonTarget:
	call CheckSubstituteOpp
	ret nz
	ld a, BATTLE_VARS_STATUS_OPP
	call GetBattleVarAddr
	and a
	ret nz
	ld a, [wTypeModifier]
	and $7f
	ret z
-	call CheckIfTargetIsPoisonType
+	ld a, POISON ; Don't poison a Poison-type
+	call CheckIfTargetIsGivenType
+	ret z
+	ld a, STEEL ; Don't poison a Steel-type
+	call CheckIfTargetIsGivenType
	ret z
	...
	ret

BattleCommand_Poison:
	ld hl, DoesntAffectText
	ld a, [wTypeModifier]
	and $7f
	jp z, .failed

-	call CheckIfTargetIsPoisonType
+	ld a, POISON
+	call CheckIfTargetIsGivenType
+	jp z, .failed
+
+	ld a, STEEL
+	call CheckIfTargetIsGivenType
	jp z, .failed

	...

We can call our modified function twice in a row to check for two different types, so long as we separately load each of those types into a before each call. Now, both Poison-types and Steel-types are immune to being poisoned, regardless of what type of move is doing the poisoning.

All that's left is to clean up the results of getting rid of CheckMoveTypeMatchesTarget by editing BattleCommand_BurnTarget...

BattleCommand_BurnTarget:
	xor a
	ld [wNumHits], a
	call CheckSubstituteOpp
	ret nz
	ld a, BATTLE_VARS_STATUS_OPP
	call GetBattleVarAddr
	and a
	jp nz, Defrost
	ld a, [wTypeModifier]
	and $7f
	ret z
-	call CheckMoveTypeMatchesTarget ; Don't burn a Fire-type
+	ld a, FIRE ; Don't burn a Fire-type
+	call CheckIfTargetIsGivenType
	ret z
	...

...and BattleCommand_FreezeTarget:

BattleCommand_FreezeTarget:
	xor a
	ld [wNumHits], a
	call CheckSubstituteOpp
	ret nz
	ld a, BATTLE_VARS_STATUS_OPP
	call GetBattleVarAddr
	and a
	ret nz
	ld a, [wTypeModifier]
	and $7f
	ret z
	ld a, [wBattleWeather]
	cp WEATHER_SUN
	ret z
-	call CheckMoveTypeMatchesTarget ; Don't freeze an Ice-type
+	ld a, ICE ; Don't freeze an Ice-type
+	call CheckIfTargetIsGivenType
	ret z
	...

Now not only have we fixed Tri Attack, but we can safely add more moves, like say Scald, without having to worry about these kinds of weird interactions. What's more, this method can easily be used to add new status immunities. We can make Electric-types immune to paralysis a la Gen 6+, or we could make Fire-types immune to freezing, Water-types immune to burns... We can even make types immune to things like critical hits or flinches, which are handled in the same file. Just remember that if you're working with the Psychic type, the constant is PSYCHIC_TYPE rather than just PSYCHIC.