Table of Contents
In the official trading card game, Pokemon's weaknesses and resistances are relatively static. If you are weak to a type, you take x2 or +20 or whatever extra damage from those attacks. Same with resistance. This makes sense for a paper game so that the rules can be more easily explained.
But here in the TCG GB, we have digitalized cards. And with hacking, it becomes possible to go beyond this system set in place and in a way that is easily understandable. Which is why in this tutorial, we create a function to have complete control over the weaknesses and resistances of every Pokémon. Each and every pokemon card can take any amount of extra damage for weakness, and any amount less damage for resistance.
1. Initial Setup
In order to add a modifier value to weaknesses and resistances easily, you have to create new categories that go into cards.asm and wram.asm. However, this will also increase the total size of every card in the file, and the fact that cards sizes are larger may mess with other parts of the game. Although it is totally possible to do, due to the inherent difficulty of modifying card sizes and everything associated with it, this tutorial will instead use a more 'universal' approach that works with any version of poketcg.
Instead of adding new data, we will re-appropriate two bytes to set up the functions: Length and Width. Most people glaze over this data when playing the game, so most people will not miss them. This lets us not change the size of cards while adding the full modifier controls.
First, delete everything that has to do with length and width, including the text just to be safe. These locations are found in:
Engine -> Duel -> Core
DisplayCardPage_PokemonDescription:
DisplayCardPage_PokemonDescription:
; print surrounding box, card name at 5,1, type, set 2, and rarity
call PrintPokemonCardPageGenericInformation
call LoadDuelCardSymbolTiles2
- ; print "LENGTH", "WEIGHT", "Lv", and "HP" where it corresponds in the page
- ld hl, CardPageLengthWeightTextData
- call PlaceTextItems
ld hl, CardPageLvHPTextTileData
call WriteDataBlocksToBGMap0
; draw the card symbol associated to its TYPE_* at 3,2
lb de, 3, 2
call DrawCardSymbol
; print the Level and HP numbers at 12,2 and 16,2 respectively
lb bc, 12, 2
ld a, [wLoadedCard1Level]
call WriteTwoDigitNumberInTxSymbolFormat
lb bc, 16, 2
ld a, [wLoadedCard1HP]
call WriteTwoByteNumberInTxSymbolFormat
; print the Pokemon's category at 1,10 (just above the length and weight texts)
lb de, 1, 10
ld hl, wLoadedCard1Category
call InitTextPrinting_ProcessTextFromPointerToID
ld a, TX_KATAKANA
call ProcessSpecialTextCharacter
ldtx hl, PokemonText
call ProcessTextFromID
- ; print the length and weight values at 5,11 and 5,12 respectively
- lb bc, 5, 11
- ld hl, wLoadedCard1Length
- ld a, [hli]
- ld l, [hl]
- ld h, a
- call PrintPokemonCardLength
- lb bc, 5, 12
- ld hl, wLoadedCard1Weight
- ld a, [hli]
- ld h, [hl]
- ld l, a
- call PrintPokemonCardWeight
- ldtx hl, LbsText
- call InitTextPrinting_ProcessTextFromID
; print the card's description without line separation
call SetNoLineSeparation
ld hl, wLoadedCard1Description
ld a, [hli]
ld h, [hl]
ld l, a
call CountLinesOfTextFromID
lb de, 1, 13
cp 4
jr nc, .print_description
inc e ; move a line down, as the description is short enough to fit in three lines
...
-CardPageLengthWeightTextData:
- textitem 1, 11, LengthText
- textitem 1, 12, WeightText
- db $ff
...
-PrintPokemonCardWeight:
- (delete this whole section)
-PrintPokemonCardLength:
- (delete this whole section)
Then, delete LengthText and WeightText found in text_offsets and text1.
Once you have deleted everything associated with length and weight, it's time to modify a few factors.
First, go to constants -> card_data_constants and alter the following:
...
; TYPE_PKMN card only
DEF CARD_DATA_RETREAT_COST EQU $32
DEF CARD_DATA_WEAKNESS EQU $33
DEF CARD_DATA_RESISTANCE EQU $34
DEF CARD_DATA_CATEGORY EQU $35
DEF CARD_DATA_POKEDEX_NUMBER EQU $37
DEF CARD_DATA_UNUSED EQU $38
DEF CARD_DATA_LEVEL EQU $39
-DEF CARD_DATA_LENGTH EQU $3a
-DEF CARD_DATA_WEIGHT EQU $3c
+DEF CARD_DATA_WEAKNESS_MOD EQU $3a
+DEF CARD_DATA_RESISTANCE_MOD EQU $3c
DEF CARD_DATA_PKMN_DESCRIPTION EQU $3e
DEF CARD_DATA_AI_INFO EQU $40
Next, go to macros -> wram.asm:
...
\1PokedexNumber:: ds 1
ds 1
\1Level:: ds 1
-\1Length:: ds 2
-\1Weight:: ds 2
+\1WeaknessMod:: ds 2
+\1ResistanceMod:: ds 2
\1Description:: ds 2
\1AIInfo:: ds 1
...
The setup is complete, now we can the actual effects.
2. Adding the New Code
To define the logic of the new code, we have to go to the the part where the game calculated weaknesses and resistances. This is found in home -> duel under ApplyDamageModifiers_DamageToTarget:: and ApplyDamageModifiers_DamageToSelf::, both of which use similar code.
ApplyDamageModifiers_DamageToTarget::
...
.affected_by_wr
call HandleDoubleDamageSubstatus
ld a, e
or d
ret z
ldh a, [hTempPlayAreaLocation_ff9d]
call GetPlayAreaCardColor
call TranslateColorToWR
ld b, a
call SwapTurn
call GetArenaCardWeakness
call SwapTurn
and b
jr z, .not_weak
- sla e
- rl d
+ call SwapTurn
+ ld a, DUELVARS_ARENA_CARD
+ call GetTurnDuelistVariable ; Gets the opponent's card info
+ call LoadCardDataToBuffer2_FromDeckIndex
+ ld a, [wLoadedCard2WeaknessMod] ; loads the new weakness modifier we just put in
+ call SwapTurn
+ ld l, a
+ ld h, 0
+ add hl, de ; loads the extra damage to de for weakness
ld hl, wDamageEffectiveness
set WEAKNESS, [hl]
.not_weak
call SwapTurn
call GetArenaCardResistance
call SwapTurn
and b
jr z, .check_pluspower_and_defender ; jump if not resistant
- ld hl, -30
+ call SwapTurn
+ ld a, DUELVARS_ARENA_CARD
+ call GetTurnDuelistVariable
+ call LoadCardDataToBuffer2_FromDeckIndex
+ ld a, [wLoadedCard2ResistanceMod] ; loads the resistance modifier
+ ld l, a
+ ld a, [wLoadedCard2ResistanceMod + 1] ; helps to process to make it a negative value
+ ld h, a
+ call SwapTurn
add hl, de ; adds the negative value to de
ld e, l
...
ApplyDamageModifiers_DamageToSelf::
...
jr z, .not_weak
- sla e
- rl d
+ call SwapTurn ; basically the same logic as before
+ ld a, DUELVARS_ARENA_CARD
+ call GetTurnDuelistVariable
+ call LoadCardDataToBuffer2_FromDeckIndex
+ ld a, [wLoadedCard2WeaknessMod]
+ call SwapTurn
+ ld l, a
+ ld h, 0
+ add hl, de
ld hl, wDamageEffectiveness
set WEAKNESS, [hl]
.not_weak
call GetArenaCardResistance
and b
jr z, .not_resistant
- ld hl, -30
+ ld a, DUELVARS_ARENA_CARD
+ call GetTurnDuelistVariable
+ call LoadCardDataToBuffer2_FromDeckIndex
+ ld a, [wLoadedCard2ResistanceMod]
+ ld l, a
+ ld a, [wLoadedCard2ResistanceMod + 1]
+ ld h, a
+ call SwapTurn
add hl, de
ld e, l
ld d, h
ld hl, wDamageEffectiveness
set RESISTANCE, [hl]
.not_resistant
ld b, CARD_LOCATION_ARENA
call ApplyAttachedPlusPower
ld b, CARD_LOCATION_ARENA
call ApplyAttachedDefender
bit 7, d ; test for underflow
ret z
.no_damage
ld de, 0
ret
The function is now active! One problem though: the AI will not understand this. Therefore, we should let the AI know what we did. Using similar code to what was just done, go to duel -> AI -> damage_calculation and input the new code.
CalculateDamage_VersusDefendingPokemon:
...
; handle weakness
ldh a, [hTempPlayAreaLocation_ff9d]
call GetPlayAreaCardColor
call TranslateColorToWR
ld b, a
call SwapTurn
call GetArenaCardWeakness
call SwapTurn
and b
jr z, .not_weak
- sla e
- rl d
+ call SwapTurn
+ ld a, DUELVARS_ARENA_CARD
+ call GetTurnDuelistVariable
+ call LoadCardDataToBuffer2_FromDeckIndex
+ ld a, [wLoadedCard2WeaknessMod]
+ call SwapTurn
+ ld l, a
+ ld h, 0
+ add hl, de
...
.not_weak
; handle resistance
call SwapTurn
call GetArenaCardResistance
call SwapTurn
and b
jr z, .not_resistant
- ld hl, -30
+ call SwapTurn
+ ld a, DUELVARS_ARENA_CARD
+ call GetTurnDuelistVariable
+ call LoadCardDataToBuffer2_FromDeckIndex
+ ld a, [wLoadedCard2ResistanceMod]
+ ld l, a
+ ld a, [wLoadedCard2ResistanceMod + 1]
+ ld h, a
+ call SwapTurn
add hl, de
ld e, l
ld d, h
...
CalculateDamage_FromDefendingPokemon:
...
.unchanged_weak
and b
jr z, .not_weak
; double de
- sla e
- rl d
+ call SwapTurn
+ ld a, DUELVARS_ARENA_CARD
+ call GetTurnDuelistVariable
+ call LoadCardDataToBuffer2_FromDeckIndex
+ ld a, [wLoadedCard2WeaknessMod]
+ call SwapTurn
+ ld l, a
+ ld h, 0
+ add hl, de
.not_weak
; handle resistance
...
.unchanged_res
and b
jr z, .not_resistant
- ld hl, -30
+ call SwapTurn ; WR MOD
+ ld a, DUELVARS_ARENA_CARD
+ call GetTurnDuelistVariable
+ call LoadCardDataToBuffer2_FromDeckIndex
+ ld a, [wLoadedCard2ResistanceMod]
+ ld l, a
+ ld a, [wLoadedCard2ResistanceMod + 1]
+ ld h, a
+ call SwapTurn
add hl, de
ld e, l
ld d, h
...
That's it, the code is now complete. However, before we get into what to do in order to completely modify any weakness and resistance to nearly any value, one more very important thing has to be done.
3. Graphical Changes
Since we just upended the weaknesses and resistances formulas, how is the player going to know what any given weakness or resistance is? After all, the game will still only show something as being weak or resistant to something and that's it. We need to go into the graphics portion in order to effectively communicate what any given weakness or resistance is on a Pokémon.
Go to Engine -> duel -> core and track down DisplayCardPage_PokemonOverview: first of all to add a new section.
DisplayCardPage_PokemonOverview:
...
.got_wr
ld a, d
ld b, 8
call PrintCardPageWeaknessesOrResistances
inc c ; 16
ld a, e
call PrintCardPageWeaknessesOrResistances
+ call CardPageWRModifiersOnlyData
ret
And as you might expect, we'll add this section. Pick a blank space somewhere in this file and add the following:
+ CardPageWRModifiersOnlyData:
+ ld a, [wLoadedCard1WeaknessMod] ; load the weakness modifier
+ cp 0 ; check against 0
+ jr z, .CheckResistance ; if 0, go to the resistance check
+ lb bc, 9, 15
+ ld a, SYM_PLUS
+ call WriteByteToBGMap0 ; otherwise make a plus symbol and add it to coordinates 9,15
+ lb bc, 10, 15 ; at coordinates 10, 15...
+ ld hl, wLoadedCard1WeaknessMod
+ ld a, [hli]
+ ld l, [hl]
+ ld h, a ; -convert the weakness modifier to a,
+ call WriteTwoDigitNumberInTxSymbolFormat ; - then write it
+.CheckResistance
+ ld a, [wLoadedCard1ResistanceMod] ; load the resistance modifier
+ cp 0
+ jr z, .Done ; if 0, go to .done
+ lb bc, 9, 16
+ ld a, SYM_MINUS
+ call WriteByteToBGMap0 ; write a minus symbol at these coordinates
+ lb bc, 10, 16
+ ld hl, wLoadedCard1ResistanceMod
+ ld a, [hli]
+ ld h, [hl]
+ ld l, a ; convert the resistance number to a
+ xor $ff
+ inc a ; convert from a negative number to a positive displayed number for the purposes of graphics
+ call WriteTwoDigitNumberInTxSymbolFormat ; and write it
+.Done
+ ret
Now, we have added data to the main page of the Pokémon card that accurately reflects changes made to the card. Like the image at the very top of this tutorial, the game will now display your weaknesses and resistances as + [number] and -[number] respectively. Furthermore, if the value is 0, it simply won't be displayed at all to eliminate unnecessary clutter.
4. How to Edit
Finally, you can now edit the values of each individual Pokémon card to be almost whatever you want. To edit in new numbers, go to any Pokémon's data, like Bulbasaur's for instance:
db 1 ; retreat cost
db WR_FIRE ; weakness
db NONE ; resistance
tx SeedName ; category
db 1 ; Pokedex number
db 0
db 13 ; level
db 2, 4 ; length
dw 15 * 10 ; weight
tx BulbasaurDescription ; description
db HAS_EVOLUTION ; AI info```
As you might expect, at first these data values will be completely wacky because we just upended length and weight, so the game will interpret 2, 4 to be something odd. You will have to edit every single Pokémon from this point forward for the game to function, sadly. But back to the editing, the first thing you must do is convert the db of "length" into dw, this allows the game to process what's going on in all the above functions. From there, edit the former length and width data to 2 digits or a 0. However, with resistance, always make it a negative number. This is because in the new resistance math, the game will add that number to de, the damage dealt by a Pokémon. Making it a positive number will result in a card taking more damage from resistance.
So, if someone wanted to give Bulbasaur an extra resistance to water while keeping the fire weakness, it would look like this:
db 1 ; retreat cost
db WR_FIRE ; weakness
+ db WR_WATER ; resistance
tx SeedName ; category
db 1 ; Pokedex number
db 0
db 13 ; level
+ dw 20 ; weakness mod, adds 20 to weakness damage
+ dw -80 ; resistance mod, subtracts 80 from resistance damage
tx BulbasaurDescription ; description
db HAS_EVOLUTION ; AI info```
There are some limitations to keep in mind while doing this. First, a limitation of the code is that the values have to be 2 digits or zero. anything from 0 to 99 is valid, 100 or more will cause graphical errors. Second, if you add multiple weaknesses or resistances, they will be the same value as the modifier you put in. For instance, you cannot give Bulbasaur a +10 weakness to fire and a +20 weakness to psychic. But aside from these two minor things, the sky is the limit when it comes to how much you want to edit the weakness and resistance data.