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

  1. Create the inverse type matchup chart
  2. Add a new battle type constant
  3. 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 INCLUDEd 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.