Skip to content

Tutorial: Adding Fairy Type

Matt edited this page May 2, 2026 · 2 revisions

Add Fairy Type

Author: stitch

Adds the Fairy-type to Platinum, originally added in X & Y. On its own, this can be used to update the types of existing Pokemon to be pure/part Fairy-type, and update existing moves. You can extend this to add moves added in later generations (Play Rough, Dazzling Gleam, etc) that are Fairy-type (note: afaik these must replace existing moves currently).

This guide will take the simplified approach: replacing TYPE_MYSTERY - otherwise known as the ??? type, the type used for Curse - and replacing it with TYPE_FAIRY. If you wish to add Fairy as a type, or add your own new types, I'd recommend the much better non-decomp guide by Yako and leave it as an extension for the reader 😅

Updating Curse

As this guide will be replacing TYPE_MYSTERY, we initially want to change the type of Curse. In later generations, Curse became a Ghost-type move specifically, so I would recommend updating Curse to that. To do this, change:

res/battle/moves/curse/data.json:

    "name": "Curse",
    "class": "CLASS_STATUS",
-   "type": "TYPE_MYSTERY",
+   "type": "TYPE_GHOST",
    "power": 0,
    "accuracy": 0,

For a more visual representation, I updated Curse to modern generation standards in this commit - this also includes other updates to Curse, such as:

  • The ability to hit through Substitute
  • Hitting a random target in Double battles

Replacing TYPE_MYSTERY with TYPE_FAIRY

Now, we're going to change the internal logic of Platinum to point TYPE_MYSTERY to TYPE_FAIRY. In fact, the easiest way to do this is a simple CTRL+F and replace all instances of TYPE_MYSTERY with TYPE_FAIRY, now that we've removed the main edge case of Curse. The following files should be affected:

  • generated/pokemon_types.txt: The enum that describes all types and assigns them effective values. Here, the replacement sets TYPE_FAIRY = 9.
  • src/applications/frontier/battle_hall/main.c: This is currently the main issue with overriding the mystery type. In Platinum, TYPE_MYSTERY is the trigger for the Matron fight. If they are available to fight, then only TYPE_MYSTERY is selectable, and this triggers the special battle. This still works as expected, but means that a Fairy-type trainer/set of trainers will not be added to the trainer pool in the Battle Hall - I invite anyone to extend this, but it wasn't my main focus for this guide.
  • src/overlay104/ov104_0223AF58.c: Related to Battle Hall.
  • src/applications/pokedex/infomain.c: A switch case to display the correct animation ID in the Pokedex. For now, it will just be the same as the Ghost type. This will be changed in a later step, but is harmless for now.
  • src/applications/poketch/move_tester/main.c: Type chart bits, we'll update the type effectivenesses in a moment.
  • src/battle/battle_lib.c: Also contains some type chart stuff, again will be fixed in a moment, but importantly this file deals with Hidden Power logic. Notably, Hidden Power cannot be Fairy or Normal-type, so replacing the mystery type with the Fairy type actually works as we want it to (Hidden Power could, of course, also not be Mystery-type).
  • src/battle/trainer_ai/trainer_ai.c: More Hidden Power logic.
  • src/overlay005/ov5_021F6454.c: More Hidden Power logic.
  • src/tv_segment.c: TV segment text excluding the Fairy-type. You could probably re-add this to the allowed type list in all honesty, I just never tested it.
  • src/type_icon.c: Just maps the icon palette textures to TYPE_FAIRY instead. We'll come back to the palette as it's still set to the Mystery sprite currently.
  • tools/dataproc/src/speciesproc.c: Data packer for the Pokedex. Currently just ignores the Mystery/Fairy type. Leave replaced for now, we'll come back to this when we tackle the Pokedex.
  • src/battle/battle_script.c: Has some more Hidden Power logic, see above. Additionally, the battle command TryConversion handles the case of Curse. You'll see two blocks that look like this:
            if (moveType == TYPE_MYSTERY) {
                if (MON_HAS_TYPE(battleCtx->attacker, TYPE_GHOST)) {
                    moveType = TYPE_GHOST;
                } else {
                    moveType = TYPE_NORMAL;
                }
            }

I have elected to remove these on the grounds that Curse is now considered a pure Ghost-type move.

Replace text

Pretty simple fix here. As we're overriding the mystery type, which usually displays as "???", we just want to replace this.

res/text/pokemon_type_names.json:

    {
      "id": "pl_msg_00000624_00009",
-     "en_US": "???"
+     "en_US": "FAIRY"
    },
    {

Replace type icon

The icon used can be found at the bottom of this section. To add this, do the following:

  1. Add the sprite to res/graphics/battle/type_icons/fairy.png
  2. Delete the pre-existing res/graphics/battle/type_icons/mystery.png
  3. We need to tell meson, the build system, that when it builds this folder, it should bundle the 'fairy.png' file:

res/graphics/battle/type_icons/meson.build:

      'normal.png',
      'poison.png',
-     'mystery.png',
+     'fairy.png',
      'rock.png',
      'steel.png',
  1. After the sprites get bundled, they get mapped to a particular file location. As we've changed the file name, we need to update the exact file path.

res/graphics/battle/sprites.order:

type_icons/normal.NCGR.lz
type_icons/poison.NCGR.lz
- type_icons/mystery.NCGR.lz
+ type_icons/fairy.NCGR.lz
type_icons/rock.NCGR.lz
type_icons/steel.NCGR.lz
  1. The icon indices need to know to find the type icons in a different file now we've changed the file name.

src/type_icon.c:

    [TYPE_GHOST]    = type_icons_ghost_NCGR_lz,
    [TYPE_STEEL]    = type_icons_steel_NCGR_lz,
-   [TYPE_MYSTERY]  = type_icons_mystery_NCGR_lz,
+   [TYPE_FAIRY]    = type_icons_fairy_NCGR_lz,
    [TYPE_FIRE]     = type_icons_fire_NCGR_lz,
    [TYPE_WATER]    = type_icons_water_NCGR_lz,
  1. The icon provided has the pink color on palette index 1, instead of 2 (which is what the mystery icon used). To update this:

src/type_icon.c:

    [TYPE_GHOST]    = 1,
    [TYPE_STEEL]    = 0,
-   [TYPE_MYSTERY]  = 2,
+   [TYPE_FAIRY]    = 1,
    [TYPE_FIRE]     = 0,
    [TYPE_WATER]    = 1,

That should be everything - if you build your ROM, you should see the type icon in battle and on the summary screen updated correctly.

fairy

Update move selection palette

When using moves in battle, you should see that the Fairy type icon is correct, but the surrounding border is the wrong color. This needs updating. Do a search for sMovePaletteMystery and you should find the following:

src/overlay011/move_palettes.c:

ALIGN_4 static const u16 sMovePaletteMystery[] = {
    RGB(13, 14, 29),
    RGB(31, 31, 31),
    RGB(26, 19, 22),
    RGB(23, 16, 19),
    RGB(21, 13, 17),
    RGB(18, 11, 15),
    RGB(16, 9, 12),
    RGB(13, 7, 10),
    RGB(11, 6, 8),
    RGB(27, 23, 25),
    RGB(6, 6, 6),
    RGB(0, 0, 0),
    RGB(12, 12, 12),
    RGB(28, 26, 13),
    RGB(30, 29, 21),
    RGB(0, 0, 0),
};

Replace this with the following:

ALIGN_4 static const u16 sMovePaletteFairy[] = {
    RGB(13, 14, 29),
    RGB(31, 31, 31),
    RGB(28, 19, 25),
    RGB(23, 16, 20),
    RGB(24, 16, 21),
    RGB(27, 16, 23),
    RGB(21, 9, 16),
    RGB(18, 7, 14),
    RGB(18, 5, 13),
    RGB(29, 22, 27),
    RGB(6, 6, 6),
    RGB(0, 0, 0),
    RGB(12, 12, 12),
    RGB(28, 26, 13),
    RGB(30, 29, 21),
    RGB(0, 0, 0),
};

And then, naturally, update the usage:

    sMovePaletteGhost,
    sMovePaletteSteel,
-   sMovePaletteMystery,
+   sMovePaletteFairy,
    sMovePaletteFire,
    sMovePaletteWater,

Rebuilding now should show the Fairy type's pink color background on move selection!

Updating type coverage

Type coverage is done in two files discussed previously. We will need to update both of these. The former is the Poketch Move Tester app, and the second is the actual type chart the battle system uses to determine type effectiveness.

To avoid large diffs, I've tried just to show what to replace:

Poketch Move Tester app

Replace sMoveTesterTypeChart with the following:

src/applications/poketch/move_tester/main.c:

static const s8 sMoveTesterTypeChart[NUM_POKEMON_TYPES][NUM_POKEMON_TYPES] = 
{ //                                                                                Defending Type
                    //  Normal Fighting Flying  Poison  Ground  Rock    Bug    Ghost    Steel   Fairy   Fire    Water   Grass  Electric Psychic Ice     Dragon  Dark
  // Attacking Type  
    [TYPE_NORMAL]   = { ______, ______, ______, ______, ______, X(0_5), ______, X(0_0), X(0_5), ______, ______, ______, ______, ______, ______, ______, ______, ______ },
    [TYPE_FIGHTING] = { X(2_0), ______, X(0_5), X(0_5), ______, X(2_0), X(0_5), X(0_0), X(2_0), X(0_5), ______, ______, ______, ______, X(0_5), X(2_0), ______, X(2_0) },
    [TYPE_FLYING]   = { ______, X(2_0), ______, ______, ______, X(0_5), X(2_0), ______, X(0_5), ______, ______, ______, X(2_0), X(0_5), ______, ______, ______, ______ },
    [TYPE_POISON]   = { ______, ______, ______, X(0_5), X(0_5), X(0_5), ______, X(0_5), X(0_0), X(2_0), ______, ______, X(2_0), ______, ______, ______, ______, ______ },
    [TYPE_GROUND]   = { ______, ______, X(0_0), X(2_0), ______, X(2_0), X(0_5), ______, X(2_0), ______, X(2_0), ______, X(0_5), X(2_0), ______, ______, ______, ______ },
    [TYPE_ROCK]     = { ______, X(0_5), X(2_0), ______, X(0_5), ______, X(2_0), ______, X(0_5), ______, X(2_0), ______, ______, ______, ______, X(2_0), ______, ______ },
    [TYPE_BUG]      = { ______, X(0_5), X(0_5), X(0_5), ______, ______, ______, X(0_5), X(0_5), X(0_5), X(0_5), ______, X(2_0), ______, X(2_0), ______, ______, X(2_0) },
    [TYPE_GHOST]    = { X(0_0), ______, ______, ______, ______, ______, ______, X(2_0), X(0_5), ______, ______, ______, ______, ______, X(2_0), ______, ______, X(0_5) },
    [TYPE_STEEL]    = { ______, ______, ______, ______, ______, X(2_0), ______, ______, X(0_5), X(2_0), X(0_5), X(0_5), ______, X(0_5), ______, X(2_0), ______, ______ },
    [TYPE_FAIRY]    = { ______, X(2_0), ______, X(0_5), ______, ______, ______, ______, X(0_5), ______, X(0_5), ______, ______, ______, ______, ______, X(2_0), X(2_0) },
    [TYPE_FIRE]     = { ______, ______, ______, ______, ______, X(0_5), X(2_0), ______, X(2_0), ______, X(0_5), X(0_5), X(2_0), ______, ______, X(2_0), X(0_5), ______ },
    [TYPE_WATER]    = { ______, ______, ______, ______, X(2_0), X(2_0), ______, ______, ______, ______, X(2_0), X(0_5), X(0_5), ______, ______, ______, X(0_5), ______ },
    [TYPE_GRASS]    = { ______, ______, X(0_5), X(0_5), X(2_0), X(2_0), X(0_5), ______, X(0_5), ______, X(0_5), X(2_0), X(0_5), ______, ______, ______, X(0_5), ______ },
    [TYPE_ELECTRIC] = { ______, ______, X(2_0), ______, X(0_0), ______, ______, ______, ______, ______, ______, X(2_0), X(0_5), X(0_5), ______, ______, X(0_5), ______ },
    [TYPE_PSYCHIC]  = { ______, X(2_0), ______, X(2_0), ______, ______, ______, ______, X(0_5), ______, ______, ______, ______, ______, X(0_5), ______, ______, X(0_0) },
    [TYPE_ICE]      = { ______, ______, X(2_0), ______, X(2_0), ______, ______, ______, X(0_5), ______, X(0_5), X(0_5), X(2_0), ______, ______, X(0_5), X(2_0), ______ },
    [TYPE_DRAGON]   = { ______, ______, ______, ______, ______, ______, ______, ______, X(0_5), X(0_0), ______, ______, ______, ______, ______, ______, X(2_0), ______ },
    [TYPE_DARK]     = { ______, X(0_5), ______, ______, ______, ______, ______, X(2_0), X(0_5), X(0_5), ______, ______, ______, ______, X(2_0), ______, ______, X(0_5) }
};

Battle System type effectiveness

Add to the sTypeMatchupMultipliers array with the Fairy-type effectiveness chart

static const u8 sTypeMatchupMultipliers[][3] = {
    { TYPE_NORMAL, TYPE_ROCK, TYPE_MULTI_NOT_VERY_EFF },
    { TYPE_NORMAL, TYPE_STEEL, TYPE_MULTI_NOT_VERY_EFF },
    { TYPE_FIRE, TYPE_FIRE, TYPE_MULTI_NOT_VERY_EFF },
    { TYPE_FIRE, TYPE_WATER, TYPE_MULTI_NOT_VERY_EFF },
    { TYPE_FIRE, TYPE_GRASS, TYPE_MULTI_SUPER_EFF },
    { TYPE_FIRE, TYPE_ICE, TYPE_MULTI_SUPER_EFF },
    { TYPE_FIRE, TYPE_BUG, TYPE_MULTI_SUPER_EFF },
    { TYPE_FIRE, TYPE_ROCK, TYPE_MULTI_NOT_VERY_EFF },
    { TYPE_FIRE, TYPE_DRAGON, TYPE_MULTI_NOT_VERY_EFF },
    { TYPE_FIRE, TYPE_STEEL, TYPE_MULTI_SUPER_EFF },
    { TYPE_WATER, TYPE_FIRE, TYPE_MULTI_SUPER_EFF },
    { TYPE_WATER, TYPE_WATER, TYPE_MULTI_NOT_VERY_EFF },
    { TYPE_WATER, TYPE_GRASS, TYPE_MULTI_NOT_VERY_EFF },
    { TYPE_WATER, TYPE_GROUND, TYPE_MULTI_SUPER_EFF },
    { TYPE_WATER, TYPE_ROCK, TYPE_MULTI_SUPER_EFF },
    { TYPE_WATER, TYPE_DRAGON, TYPE_MULTI_NOT_VERY_EFF },
    { TYPE_ELECTRIC, TYPE_WATER, TYPE_MULTI_SUPER_EFF },
    { TYPE_ELECTRIC, TYPE_ELECTRIC, TYPE_MULTI_NOT_VERY_EFF },
    { TYPE_ELECTRIC, TYPE_GRASS, TYPE_MULTI_NOT_VERY_EFF },
    { TYPE_ELECTRIC, TYPE_GROUND, TYPE_MULTI_IMMUNE },
    { TYPE_ELECTRIC, TYPE_FLYING, TYPE_MULTI_SUPER_EFF },
    { TYPE_ELECTRIC, TYPE_DRAGON, TYPE_MULTI_NOT_VERY_EFF },
    { TYPE_GRASS, TYPE_FIRE, TYPE_MULTI_NOT_VERY_EFF },
    { TYPE_GRASS, TYPE_WATER, TYPE_MULTI_SUPER_EFF },
    { TYPE_GRASS, TYPE_GRASS, TYPE_MULTI_NOT_VERY_EFF },
    { TYPE_GRASS, TYPE_POISON, TYPE_MULTI_NOT_VERY_EFF },
    { TYPE_GRASS, TYPE_GROUND, TYPE_MULTI_SUPER_EFF },
    { TYPE_GRASS, TYPE_FLYING, TYPE_MULTI_NOT_VERY_EFF },
    { TYPE_GRASS, TYPE_BUG, TYPE_MULTI_NOT_VERY_EFF },
    { TYPE_GRASS, TYPE_ROCK, TYPE_MULTI_SUPER_EFF },
    { TYPE_GRASS, TYPE_DRAGON, TYPE_MULTI_NOT_VERY_EFF },
    { TYPE_GRASS, TYPE_STEEL, TYPE_MULTI_NOT_VERY_EFF },
    { TYPE_ICE, TYPE_WATER, TYPE_MULTI_NOT_VERY_EFF },
    { TYPE_ICE, TYPE_GRASS, TYPE_MULTI_SUPER_EFF },
    { TYPE_ICE, TYPE_ICE, TYPE_MULTI_NOT_VERY_EFF },
    { TYPE_ICE, TYPE_GROUND, TYPE_MULTI_SUPER_EFF },
    { TYPE_ICE, TYPE_FLYING, TYPE_MULTI_SUPER_EFF },
    { TYPE_ICE, TYPE_DRAGON, TYPE_MULTI_SUPER_EFF },
    { TYPE_ICE, TYPE_STEEL, TYPE_MULTI_NOT_VERY_EFF },
    { TYPE_ICE, TYPE_FIRE, TYPE_MULTI_NOT_VERY_EFF },
    { TYPE_FIGHTING, TYPE_NORMAL, TYPE_MULTI_SUPER_EFF },
    { TYPE_FIGHTING, TYPE_ICE, TYPE_MULTI_SUPER_EFF },
    { TYPE_FIGHTING, TYPE_POISON, TYPE_MULTI_NOT_VERY_EFF },
    { TYPE_FIGHTING, TYPE_FLYING, TYPE_MULTI_NOT_VERY_EFF },
    { TYPE_FIGHTING, TYPE_PSYCHIC, TYPE_MULTI_NOT_VERY_EFF },
    { TYPE_FIGHTING, TYPE_BUG, TYPE_MULTI_NOT_VERY_EFF },
    { TYPE_FIGHTING, TYPE_ROCK, TYPE_MULTI_SUPER_EFF },
    { TYPE_FIGHTING, TYPE_DARK, TYPE_MULTI_SUPER_EFF },
    { TYPE_FIGHTING, TYPE_STEEL, TYPE_MULTI_SUPER_EFF },
    { TYPE_FIGHTING, TYPE_FAIRY, TYPE_MULTI_NOT_VERY_EFF },
    { TYPE_POISON, TYPE_GRASS, TYPE_MULTI_SUPER_EFF },
    { TYPE_POISON, TYPE_POISON, TYPE_MULTI_NOT_VERY_EFF },
    { TYPE_POISON, TYPE_GROUND, TYPE_MULTI_NOT_VERY_EFF },
    { TYPE_POISON, TYPE_ROCK, TYPE_MULTI_NOT_VERY_EFF },
    { TYPE_POISON, TYPE_GHOST, TYPE_MULTI_NOT_VERY_EFF },
    { TYPE_POISON, TYPE_STEEL, TYPE_MULTI_IMMUNE },
    { TYPE_POISON, TYPE_FAIRY, TYPE_MULTI_SUPER_EFF },
    { TYPE_GROUND, TYPE_FIRE, TYPE_MULTI_SUPER_EFF },
    { TYPE_GROUND, TYPE_ELECTRIC, TYPE_MULTI_SUPER_EFF },
    { TYPE_GROUND, TYPE_GRASS, TYPE_MULTI_NOT_VERY_EFF },
    { TYPE_GROUND, TYPE_POISON, TYPE_MULTI_SUPER_EFF },
    { TYPE_GROUND, TYPE_FLYING, TYPE_MULTI_IMMUNE },
    { TYPE_GROUND, TYPE_BUG, TYPE_MULTI_NOT_VERY_EFF },
    { TYPE_GROUND, TYPE_ROCK, TYPE_MULTI_SUPER_EFF },
    { TYPE_GROUND, TYPE_STEEL, TYPE_MULTI_SUPER_EFF },
    { TYPE_FLYING, TYPE_ELECTRIC, TYPE_MULTI_NOT_VERY_EFF },
    { TYPE_FLYING, TYPE_GRASS, TYPE_MULTI_SUPER_EFF },
    { TYPE_FLYING, TYPE_FIGHTING, TYPE_MULTI_SUPER_EFF },
    { TYPE_FLYING, TYPE_BUG, TYPE_MULTI_SUPER_EFF },
    { TYPE_FLYING, TYPE_ROCK, TYPE_MULTI_NOT_VERY_EFF },
    { TYPE_FLYING, TYPE_STEEL, TYPE_MULTI_NOT_VERY_EFF },
    { TYPE_PSYCHIC, TYPE_FIGHTING, TYPE_MULTI_SUPER_EFF },
    { TYPE_PSYCHIC, TYPE_POISON, TYPE_MULTI_SUPER_EFF },
    { TYPE_PSYCHIC, TYPE_PSYCHIC, TYPE_MULTI_NOT_VERY_EFF },
    { TYPE_PSYCHIC, TYPE_DARK, TYPE_MULTI_IMMUNE },
    { TYPE_PSYCHIC, TYPE_STEEL, TYPE_MULTI_NOT_VERY_EFF },
    { TYPE_BUG, TYPE_FIRE, TYPE_MULTI_NOT_VERY_EFF },
    { TYPE_BUG, TYPE_GRASS, TYPE_MULTI_SUPER_EFF },
    { TYPE_BUG, TYPE_FIGHTING, TYPE_MULTI_NOT_VERY_EFF },
    { TYPE_BUG, TYPE_POISON, TYPE_MULTI_NOT_VERY_EFF },
    { TYPE_BUG, TYPE_FLYING, TYPE_MULTI_NOT_VERY_EFF },
    { TYPE_BUG, TYPE_PSYCHIC, TYPE_MULTI_SUPER_EFF },
    { TYPE_BUG, TYPE_GHOST, TYPE_MULTI_NOT_VERY_EFF },
    { TYPE_BUG, TYPE_DARK, TYPE_MULTI_SUPER_EFF },
    { TYPE_BUG, TYPE_STEEL, TYPE_MULTI_NOT_VERY_EFF },
    { TYPE_BUG, TYPE_FAIRY, TYPE_MULTI_NOT_VERY_EFF },
    { TYPE_ROCK, TYPE_FIRE, TYPE_MULTI_SUPER_EFF },
    { TYPE_ROCK, TYPE_ICE, TYPE_MULTI_SUPER_EFF },
    { TYPE_ROCK, TYPE_FIGHTING, TYPE_MULTI_NOT_VERY_EFF },
    { TYPE_ROCK, TYPE_GROUND, TYPE_MULTI_NOT_VERY_EFF },
    { TYPE_ROCK, TYPE_FLYING, TYPE_MULTI_SUPER_EFF },
    { TYPE_ROCK, TYPE_BUG, TYPE_MULTI_SUPER_EFF },
    { TYPE_ROCK, TYPE_STEEL, TYPE_MULTI_NOT_VERY_EFF },
    { TYPE_GHOST, TYPE_NORMAL, TYPE_MULTI_IMMUNE },
    { TYPE_GHOST, TYPE_PSYCHIC, TYPE_MULTI_SUPER_EFF },
    { TYPE_GHOST, TYPE_DARK, TYPE_MULTI_NOT_VERY_EFF },
    { TYPE_GHOST, TYPE_GHOST, TYPE_MULTI_SUPER_EFF },
    { TYPE_DRAGON, TYPE_DRAGON, TYPE_MULTI_SUPER_EFF },
    { TYPE_DRAGON, TYPE_STEEL, TYPE_MULTI_NOT_VERY_EFF },
    { TYPE_DRAGON, TYPE_FAIRY, TYPE_MULTI_IMMUNE },
    { TYPE_DARK, TYPE_FIGHTING, TYPE_MULTI_NOT_VERY_EFF },
    { TYPE_DARK, TYPE_PSYCHIC, TYPE_MULTI_SUPER_EFF },
    { TYPE_DARK, TYPE_GHOST, TYPE_MULTI_SUPER_EFF },
    { TYPE_DARK, TYPE_DARK, TYPE_MULTI_NOT_VERY_EFF },
    { TYPE_DARK, TYPE_FAIRY, TYPE_MULTI_NOT_VERY_EFF },
    { TYPE_STEEL, TYPE_FIRE, TYPE_MULTI_NOT_VERY_EFF },
    { TYPE_STEEL, TYPE_WATER, TYPE_MULTI_NOT_VERY_EFF },
    { TYPE_STEEL, TYPE_ELECTRIC, TYPE_MULTI_NOT_VERY_EFF },
    { TYPE_STEEL, TYPE_ICE, TYPE_MULTI_SUPER_EFF },
    { TYPE_STEEL, TYPE_ROCK, TYPE_MULTI_SUPER_EFF },
    { TYPE_STEEL, TYPE_STEEL, TYPE_MULTI_NOT_VERY_EFF },
    { TYPE_STEEL, TYPE_FAIRY, TYPE_MULTI_SUPER_EFF },
    { TYPE_FAIRY, TYPE_FIRE, TYPE_MULTI_NOT_VERY_EFF },
    { TYPE_FAIRY, TYPE_FIGHTING, TYPE_MULTI_SUPER_EFF },
    { TYPE_FAIRY, TYPE_POISON, TYPE_MULTI_NOT_VERY_EFF },
    { TYPE_FAIRY, TYPE_DRAGON, TYPE_MULTI_SUPER_EFF },
    { TYPE_FAIRY, TYPE_DARK, TYPE_MULTI_SUPER_EFF },
    { TYPE_FAIRY, TYPE_STEEL, TYPE_MULTI_NOT_VERY_EFF },

    { 0xFE, 0xFE, TYPE_MULTI_IMMUNE },

    // These values are separated from the remainder of the table to support
    // the Foresight effect, which removes these immunities from consideration.
    { TYPE_NORMAL, TYPE_GHOST, TYPE_MULTI_IMMUNE },
    { TYPE_FIGHTING, TYPE_GHOST, TYPE_MULTI_IMMUNE },

    { 0xFF, 0xFF, TYPE_MULTI_IMMUNE },
};

The Fairy type is now usable in Platinum! The only extension, which is very much necessary, is now updating the Pokedex to work correctly.

Pokedex updates

I'll chunk this into lots of small updates, as there's quite a bit to cover:

Type image, animations and sorting

Currently the way the types are stored in the decomp is.. unique. There is essentially a large, sorted PNG that contains all of the type icon images, and at the bottom contains the yellow background on a Pokedex entry. We want to add the Fairy type sprite to this list carefully, without messing with the palette. To ease this process, I've attached the updated type icon image, animation & cell JSON files, and the type_icons palette file. To summarise the changes in these files:

  1. Types were effectively sorted randomly before. If you dump your zukan.narc from any built ROM, and then load files 0013, 0088, 0089 and 0090 into NitroPaint, this is quite obvious. It makes each type a bit difficult to refer to, so we're going to sort them into the order the enum describes in generated/pokemon_types.txt. We can see this ordering if you view the diff of type_icons_anim.json.
  2. The Fairy type's pink color is not something currently used in the palette. We're adding this to sub-palette with ID 5, and in a further bullet point we'll need to update some code to fetch sub-palettes 0-5 instead of 0-4, so it isn't still zeroed in VRAM and cause it to be uncoloured.
  3. Adding the Fairy-type icon as a legal type icon

Here are the files to update:

res/graphics/pokedex/type_icons.pal:

148 189 156
255 255 139
+ 205 255 213
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
0 0 0
+ 230 148 255
+ 246 213 255
+ 189 74 230
0 0 0
0 0 0
0 0 0
+ 90 98 98
+ 255 255 255
+ 115 123 123
- 0 0 0
- 0 0 0
- 0 0 0
- 0 0 0
- 0 0 0
- 0 0 0
- 0 0 0
0 0 0
0 0 0

res/graphics/pokedex/type_icons.png:

type_icons

res/graphics/pokedex/type_icons_anim.json:

type_icons_anim.json

res/graphics/pokedex/type_icons_cell.json:

type_icons_cell.json

Fetch sub-palette 5

As I mentioned before, we added the Fairy-type color to sub-palette 5, so we need to fetch that sub-palette when we load them in. Update the following:

src/applications/pokedex/infomain.c:

-   graphicsStruct->typeIconResources[SPRITE_RESOURCE_PLTT] = SpriteResourceCollection_AddPaletteFrom(gfxData->spriteResourceCollection[SPRITE_RESOURCE_PLTT], pokedexNarc, type_icons_NCLR, FALSE, type_icons_NCLR + POKEDEX_TYPE_ICON_RESOURCE_OFFSET, NNS_G2D_VRAM_TYPE_2DMAIN, 5, heapID);
+   graphicsStruct->typeIconResources[SPRITE_RESOURCE_PLTT] = SpriteResourceCollection_AddPaletteFrom(gfxData->spriteResourceCollection[SPRITE_RESOURCE_PLTT], pokedexNarc, type_icons_NCLR, FALSE, type_icons_NCLR + POKEDEX_TYPE_ICON_RESOURCE_OFFSET, NNS_G2D_VRAM_TYPE_2DMAIN, 6, heapID);

src/applications/pokedex/infomain_foreign.c:

-   graphicsStruct->spriteResources[SPRITE_RESOURCE_PLTT] = SpriteResourceCollection_AddPaletteFrom(pokedexGraphicData->spriteResourceCollection[SPRITE_RESOURCE_PLTT], narc, type_icons_NCLR, FALSE, type_icons_NCLR + POKEDEX_TYPE_ICON_RESOURCE_OFFSET, NNS_G2D_VRAM_TYPE_2DMAIN, 5, heapID);
+   graphicsStruct->spriteResources[SPRITE_RESOURCE_PLTT] = SpriteResourceCollection_AddPaletteFrom(pokedexGraphicData->spriteResourceCollection[SPRITE_RESOURCE_PLTT], narc, type_icons_NCLR, FALSE, type_icons_NCLR + POKEDEX_TYPE_ICON_RESOURCE_OFFSET, NNS_G2D_VRAM_TYPE_2DMAIN, 6, heapID);

src/applications/pokedex/ov21_021E8D48.c:

-   ov21_021E9344(param0->unk_08, param1, param3, param4, type_icons_NCGR_lz, type_icons_NCLR, type_icons_cell_NCER_lz, type_icons_anim_NANR_lz, 5, 17000);
+   ov21_021E9344(param0->unk_08, param1, param3, param4, type_icons_NCGR_lz, type_icons_NCLR, type_icons_cell_NCER_lz, type_icons_anim_NANR_lz, 6, 17000);

Pack the Fairy-type

The Mystery type was understandably not packed with the Pokedex, as no Pokemon are of the Mystery type. Remove the following line:

tools/dataproc/src/speciesproc.c:

    // Indices of species by their types
    for (size_t i = 0; i < NUM_POKEMON_TYPES; i++) {
-       if (i == TYPE_MYSTERY) continue; // NOTE: The Mystery type is not packed
        nitroarc_ppack(p, indexed_by_type[i], count_by_type[i] * sizeof(**indexed_by_type), NULL);
    }

In the same file, also update:

tools/dataproc/src/speciesproc.c:

    archives[2].num_files = (u16)len_registry;
    archives[3].num_files = (u16)(4 * NATIONAL_DEX_MAX);
-   archives[4].num_files = (u16)(27 + (NUM_POKEMON_TYPES - 1) + NUM_BODY_SHAPES); // NOTE: The Mystery type is not packed
-   archives[5].num_files = (u16)(27 + (NUM_POKEMON_TYPES - 1) + NUM_BODY_SHAPES);
+   archives[4].num_files = (u16)(27 + NUM_POKEMON_TYPES + NUM_BODY_SHAPES);
+   archives[5].num_files = (u16)(27 + NUM_POKEMON_TYPES + NUM_BODY_SHAPES);

    common_init(DATAPROC_F_JSON, enums, archives, headers, textbanks, __FILE__, depfile_fpath, output_dir, pre_init, post_init);

Animation ID

Now that Mystery and Ghost don't need to share the same icon, we can update the getter function to fetch a unique animation ID for the Fairy-type. Recall earlier that we sorted the type sequences into the type order from the generated/pokemon_types.txt enum, so we can reap the benefits of that here:

src/applications/pokedex/infomain.c:

int PokedexGraphics_GetAnimIDfromType(int monType)
{
-   int animID;
-
-   switch (monType) {
-   case TYPE_NORMAL:
-       animID = 0;
-       break;
-   case TYPE_FIGHTING:
-       animID = 6;
-       break;
-   case TYPE_FLYING:
-       animID = 14;
-       break;
-   case TYPE_POISON:
-       animID = 10;
-       break;
-   case TYPE_GROUND:
-       animID = 8;
-       break;
-   case TYPE_ROCK:
-       animID = 5;
-       break;
-   case TYPE_BUG:
-       animID = 11;
-       break;
-   case TYPE_GHOST:
-   case TYPE_MYSTERY:
-       animID = 7;
-       break;
-   case TYPE_STEEL:
-       animID = 9;
-       break;
-   case TYPE_FIRE:
-       animID = 1;
-       break;
-   case TYPE_WATER:
-       animID = 3;
-       break;
-   case TYPE_GRASS:
-       animID = 2;
-       break;
-   case TYPE_ELECTRIC:
-       animID = 4;
-       break;
-   case TYPE_PSYCHIC:
-       animID = 15;
-       break;
-   case TYPE_ICE:
-       animID = 13;
-       break;
-   case TYPE_DRAGON:
-       animID = 16;
-       break;
-   case TYPE_DARK:
-       animID = 12;
-       break;
-   }

-   return animID;
+   return monType + 1;
}

As part of this, we've also moved the yellow background sprite to index 0, so we should update the value of `POKEDEX_TYPE_ICON_BACKGROUND_BOX_CELL`. This value should be in two files: `src/applications/pokedex/infomain.c` and `src/applications/pokedex/infomain_foreign.c`:

```diff
- #define POKEDEX_TYPE_ICON_BACKGROUND_BOX_CELL 0x11
+ #define POKEDEX_TYPE_ICON_BACKGROUND_BOX_CELL 0x00

Thanks for reading!

My fork is available at https://github.com/matt-newhall/plat-hack-decomp/ if you wish to read further into what I've been getting up to.

Clone this wiki locally