Inverse Battles - pret/pokecrystal GitHub Wiki
Inverse battles are a mechanic introduced in Generation 6, they are battles where the type matchup chart is inverted and immunities are absent.
Adding inverse battles is a fairly straightforward process which can be replicated for any battle type that modifies type effectiveness.
Contents
- Create the inverse type matchup chart
- Add a new battle type constant
- Move type matchups to a different bank
1. Create the inverse type matchup chart
Edit data/types/type_matchups.asm:
...
+InverseTypeMatchups:
+ ; attacker, defender, *=
+ db NORMAL, ROCK, SUPER_EFFECTIVE
+ db NORMAL, STEEL, SUPER_EFFECTIVE
+ db FIRE, FIRE, SUPER_EFFECTIVE
+ db FIRE, WATER, SUPER_EFFECTIVE
+ db FIRE, GRASS, NOT_VERY_EFFECTIVE
+ db FIRE, ICE, NOT_VERY_EFFECTIVE
+ db FIRE, BUG, NOT_VERY_EFFECTIVE
+ db FIRE, ROCK, SUPER_EFFECTIVE
+ db FIRE, DRAGON, SUPER_EFFECTIVE
+ db FIRE, STEEL, NOT_VERY_EFFECTIVE
+ db WATER, FIRE, NOT_VERY_EFFECTIVE
+ db WATER, WATER, SUPER_EFFECTIVE
+ db WATER, GRASS, SUPER_EFFECTIVE
+ db WATER, GROUND, NOT_VERY_EFFECTIVE
+ db WATER, ROCK, NOT_VERY_EFFECTIVE
+ db WATER, DRAGON, SUPER_EFFECTIVE
+ db ELECTRIC, WATER, NOT_VERY_EFFECTIVE
+ db ELECTRIC, ELECTRIC, SUPER_EFFECTIVE
+ db ELECTRIC, GRASS, SUPER_EFFECTIVE
+ db ELECTRIC, GROUND, SUPER_EFFECTIVE
+ db ELECTRIC, FLYING, NOT_VERY_EFFECTIVE
+ db ELECTRIC, DRAGON, SUPER_EFFECTIVE
+ db GRASS, FIRE, SUPER_EFFECTIVE
+ db GRASS, WATER, NOT_VERY_EFFECTIVE
+ db GRASS, GRASS, SUPER_EFFECTIVE
+ db GRASS, POISON, SUPER_EFFECTIVE
+ db GRASS, GROUND, NOT_VERY_EFFECTIVE
+ db GRASS, FLYING, SUPER_EFFECTIVE
+ db GRASS, BUG, SUPER_EFFECTIVE
+ db GRASS, ROCK, NOT_VERY_EFFECTIVE
+ db GRASS, DRAGON, SUPER_EFFECTIVE
+ db GRASS, STEEL, SUPER_EFFECTIVE
+ db ICE, WATER, SUPER_EFFECTIVE
+ db ICE, GRASS, NOT_VERY_EFFECTIVE
+ db ICE, ICE, SUPER_EFFECTIVE
+ db ICE, GROUND, NOT_VERY_EFFECTIVE
+ db ICE, FLYING, NOT_VERY_EFFECTIVE
+ db ICE, DRAGON, NOT_VERY_EFFECTIVE
+ db ICE, STEEL, SUPER_EFFECTIVE
+ db ICE, FIRE, SUPER_EFFECTIVE
+ db FIGHTING, NORMAL, NOT_VERY_EFFECTIVE
+ db FIGHTING, ICE, NOT_VERY_EFFECTIVE
+ db FIGHTING, POISON, SUPER_EFFECTIVE
+ db FIGHTING, FLYING, SUPER_EFFECTIVE
+ db FIGHTING, PSYCHIC_TYPE, SUPER_EFFECTIVE
+ db FIGHTING, BUG, SUPER_EFFECTIVE
+ db FIGHTING, ROCK, NOT_VERY_EFFECTIVE
+ db FIGHTING, DARK, NOT_VERY_EFFECTIVE
+ db FIGHTING, STEEL, NOT_VERY_EFFECTIVE
+ db POISON, GRASS, NOT_VERY_EFFECTIVE
+ db POISON, POISON, SUPER_EFFECTIVE
+ db POISON, GROUND, SUPER_EFFECTIVE
+ db POISON, ROCK, SUPER_EFFECTIVE
+ db POISON, GHOST, SUPER_EFFECTIVE
+ db POISON, STEEL, SUPER_EFFECTIVE
+ db GROUND, FIRE, NOT_VERY_EFFECTIVE
+ db GROUND, ELECTRIC, NOT_VERY_EFFECTIVE
+ db GROUND, GRASS, SUPER_EFFECTIVE
+ db GROUND, POISON, NOT_VERY_EFFECTIVE
+ db GROUND, FLYING, SUPER_EFFECTIVE
+ db GROUND, BUG, SUPER_EFFECTIVE
+ db GROUND, ROCK, NOT_VERY_EFFECTIVE
+ db GROUND, STEEL, NOT_VERY_EFFECTIVE
+ db FLYING, ELECTRIC, SUPER_EFFECTIVE
+ db FLYING, GRASS, NOT_VERY_EFFECTIVE
+ db FLYING, FIGHTING, NOT_VERY_EFFECTIVE
+ db FLYING, BUG, NOT_VERY_EFFECTIVE
+ db FLYING, ROCK, SUPER_EFFECTIVE
+ db FLYING, STEEL, SUPER_EFFECTIVE
+ db PSYCHIC_TYPE, FIGHTING, NOT_VERY_EFFECTIVE
+ db PSYCHIC_TYPE, POISON, NOT_VERY_EFFECTIVE
+ db PSYCHIC_TYPE, PSYCHIC_TYPE, SUPER_EFFECTIVE
+ db PSYCHIC_TYPE, DARK, SUPER_EFFECTIVE
+ db PSYCHIC_TYPE, STEEL, SUPER_EFFECTIVE
+ db BUG, FIRE, SUPER_EFFECTIVE
+ db BUG, GRASS, NOT_VERY_EFFECTIVE
+ db BUG, FIGHTING, SUPER_EFFECTIVE
+ db BUG, POISON, SUPER_EFFECTIVE
+ db BUG, FLYING, SUPER_EFFECTIVE
+ db BUG, PSYCHIC_TYPE, NOT_VERY_EFFECTIVE
+ db BUG, GHOST, SUPER_EFFECTIVE
+ db BUG, DARK, NOT_VERY_EFFECTIVE
+ db BUG, STEEL, SUPER_EFFECTIVE
+ db ROCK, FIRE, NOT_VERY_EFFECTIVE
+ db ROCK, ICE, NOT_VERY_EFFECTIVE
+ db ROCK, FIGHTING, SUPER_EFFECTIVE
+ db ROCK, GROUND, SUPER_EFFECTIVE
+ db ROCK, FLYING, NOT_VERY_EFFECTIVE
+ db ROCK, BUG, NOT_VERY_EFFECTIVE
+ db ROCK, STEEL, SUPER_EFFECTIVE
+ db GHOST, NORMAL, SUPER_EFFECTIVE
+ db GHOST, PSYCHIC_TYPE, NOT_VERY_EFFECTIVE
+ db GHOST, DARK, SUPER_EFFECTIVE
+ db GHOST, STEEL, SUPER_EFFECTIVE
+ db GHOST, GHOST, NOT_VERY_EFFECTIVE
+ db DRAGON, DRAGON, NOT_VERY_EFFECTIVE
+ db DRAGON, STEEL, SUPER_EFFECTIVE
+ db DARK, FIGHTING, SUPER_EFFECTIVE
+ db DARK, PSYCHIC_TYPE, NOT_VERY_EFFECTIVE
+ db DARK, GHOST, NOT_VERY_EFFECTIVE
+ db DARK, DARK, SUPER_EFFECTIVE
+ db DARK, STEEL, SUPER_EFFECTIVE
+ db STEEL, FIRE, SUPER_EFFECTIVE
+ db STEEL, WATER, SUPER_EFFECTIVE
+ db STEEL, ELECTRIC, SUPER_EFFECTIVE
+ db STEEL, ICE, NOT_VERY_EFFECTIVE
+ db STEEL, ROCK, NOT_VERY_EFFECTIVE
+ db STEEL, STEEL, SUPER_EFFECTIVE
+ db NORMAL, GHOST, SUPER_EFFECTIVE
+ db FIGHTING, GHOST, SUPER_EFFECTIVE
+ db -1 ; end
This is simply a copy of the normal type chart with inverted effectiveness.
Since there are no immunities we can remove the -2 that is used by Foresight.
2. Add a new battle type constant
Edit constants/battle_constants.asm
...
; battle types (wBattleType values)
const_def
const BATTLETYPE_NORMAL
const BATTLETYPE_CANLOSE
const BATTLETYPE_DEBUG
const BATTLETYPE_TUTORIAL
const BATTLETYPE_FISH
const BATTLETYPE_ROAMING
const BATTLETYPE_CONTEST
const BATTLETYPE_FORCESHINY
const BATTLETYPE_TREE
const BATTLETYPE_TRAP
const BATTLETYPE_FORCEITEM
const BATTLETYPE_CELEBI
const BATTLETYPE_SUICUNE
+ const BATTLETYPE_INVERSE
...
We'll use this new battle type to tell which chart we need to look up before the type effectiveness calculation.
Edit engine/battle/effect_commands.asm
...
BattleCommand_Stab:
...
.SkipStab:
ld a, BATTLE_VARS_MOVE_TYPE
call GetBattleVar
ld b, a
+ ld a, [wBattleType]
+ cp BATTLETYPE_INVERSE
+ jr z, .inverse
ld hl, TypeMatchups
+ jr .TypesLoop
+.inverse
+ ld hl, InverseTypeMatchups
.TypesLoop:
ld a, [hli]
...
BattleCheckTypeMatchup:
ld hl, wEnemyMonType1
ldh a, [hBattleTurn]
and a
jr z, CheckTypeMatchup
ld hl, wBattleMonType1
; fallthrough
CheckTypeMatchup:
; BUG: AI makes a false assumption about CheckTypeMatchup (see docs/bugs_and_glitches.md)
push hl
push de
push bc
ld a, BATTLE_VARS_MOVE_TYPE
call GetBattleVar
ld d, a
ld b, [hl]
inc hl
ld c, [hl]
ld a, EFFECTIVE
ld [wTypeMatchup], a
+ ld a, [wBattleType]
+ cp BATTLETYPE_INVERSE
+ jr z, .inverse
ld hl, TypeMatchups
+ jr .TypesLoop
+.inverse
+ ld hl, InverseTypeMatchups
.TypesLoop:
ld a, [hli]
...
Now the appropriate table is loaded whenever a type matchup calculation is started during an inverse battle.
There is a small problem though: the duplicate matchup table takes a lot of space and it's very likely that it pushed the size of the section over the bank size.
If we try to compile now RGBDS will complain that Section 'Effect Commands' grew too big
, which means we have to move some data in another bank.
3. Move type matchups to a different bank
Edit engine/battle/effect_commands.asm:
BattleCommand_ResetTypeMatchup:
; Reset the type matchup multiplier to 1.0, if the type matchup is not 0.
; If there is immunity in play, the move automatically misses.
call BattleCheckTypeMatchup
ld a, [wTypeMatchup]
and a
ld a, EFFECTIVE
jr nz, .reset
call ResetDamage
xor a
ld [wTypeModifier], a
inc a
ld [wAttackMissed], a
ret
.reset
ld [wTypeMatchup], a
ret
INCLUDE "engine/battle/ai/switch.asm"
-INCLUDE "data/types/type_matchups.asm"
Now that data/types/type_matchups.asm is no longer INCLUDE
d in engine/battle/effect_commands.asm, we need to tell RGBDS to add it somewhere in the ROM.
Edit main.asm:
SECTION "Effect Commands", ROMX
INCLUDE "engine/battle/effect_commands.asm"
+SECTION "Type Matchups", ROMX
+
+INCLUDE "data/types/type_matchups.asm"
SECTION "Enemy Trainers", ROMX
INCLUDE "engine/battle/ai/items.asm"
INCLUDE "engine/battle/ai/scoring.asm"
INCLUDE "engine/battle/read_trainer_attributes.asm"
INCLUDE "engine/battle/read_trainer_party.asm"
Since this new section isn't specified anywhere in layout.link, RGBDS will place it wherever there is enough free space in a bank.
Finally, we need to adjust BattleCommand_Stab
and CheckTypeMatchup
to read the tables from a different bank.
Edit engine/battle/effect_commands.asm:
...
+GetNextTypeMatchupsByte:
+ ld a, BANK(TypeMatchups)
+ call GetFarByte
+ ret
BattleCommand_Stab:
...
.TypesLoop:
- ld a, [hli]
+ call GetNextTypeMatchupsByte
+ inc hl
cp -1
jr z, .end
; foresight
cp -2
jr nz, .SkipForesightCheck
ld a, BATTLE_VARS_SUBSTATUS1_OPP
call GetBattleVar
bit SUBSTATUS_IDENTIFIED, a
jr nz, .end
jr .TypesLoop
.SkipForesightCheck:
cp b
jr nz, .SkipType
- ld a, [hl]
+ call GetNextTypeMatchupsByte
cp d
jr z, .GotMatchup
cp e
jr z, .GotMatchup
jr .SkipType
.GotMatchup:
push hl
push bc
inc hl
ld a, [wTypeModifier]
and %10000000
ld b, a
; If the target is immune to the move, treat it as a miss and calculate the damage as 0
- ld a, [hl]
+ call GetNextTypeMatchupsByte
and a
jr nz, .NotImmune
inc a
or b
ld [wTypeModifier], a
ret
...
CheckTypeMatchup:
...
.TypesLoop:
- ld a, [hli]
+ call GetNextTypeMatchupsByte
+ inc hl
cp -1
jr z, .End
cp -2
jr nz, .Next
ld a, BATTLE_VARS_SUBSTATUS1_OPP
call GetBattleVar
bit SUBSTATUS_IDENTIFIED, a
jr nz, .End
jr .TypesLoop
.Next:
cp d
jr nz, .Nope
- ld a, [hli]
+ call GetNextTypeMatchupsByte
+ inc hl
cp b
jr z, .Yup
cp c
jr z, .Yup
jr .Nope2
.Nope:
inc hl
.Nope2:
inc hl
jr .TypesLoop
.Yup:
xor a
ldh [hDividend + 0], a
ldh [hMultiplicand + 0], a
ldh [hMultiplicand + 1], a
- ld a, [hli]
+ call GetNextTypeMatchupsByte
+ inc hl
ldh [hMultiplicand + 2], a
ld a, [wTypeMatchup]
ldh [hMultiplier], a
call Multiply
ld a, 10
ldh [hDivisor], a
push bc
ld b, 4
call Divide
pop bc
ldh a, [hQuotient + 3]
ld [wTypeMatchup], a
jr .TypesLoop
.End:
pop bc
pop de
pop hl
ret
Inverse battles should now be fully functional.
You can check the rival encounter script in maps/CherrygroveCity.asm or the red Gyarados one in maps/LakeOfRage.asm for examples on how to apply a battle type to your encounters.