In Gen 2 (and Gen 3), some types were always physical and some were always special. Here's how to implement the physical/special split from Gen 4 and up. (The code for this feature was adapted from [Pokémon Fractal](https://gitgud.io/pfero/axyllagame/) (the Axyllia region) and [Polished Crystal](https://github.com/Rangi42/polishedcrystal).) ## Contents 1. [Define new constants](#1-define-new-constants) 2. [Update moves with their categories](#2-update-moves-with-their-categories) 3. [Mask out the category in `PrintMoveType`](#3-mask-out-the-category-in-printmovetype) 4. [Mask out the category in nine places in the battle engine](#4-mask-out-the-category-in-nine-places-in-the-battle-engine) 5. [Make Hidden Power special](#5-make-hidden-power-special) 6. [Update the AI to understand categories](#6-update-the-ai-to-understand-categories) 7. [Support printing category names](#7-support-printing-category-names) 8. [Display categories in battle](#8-display-categories-in-battle) 9. [Display categories in the Move screen](#9-display-categories-in-the-move-screen) ## 1. Define new constants Edit [constants/type_constants.asm](../blob/master/constants/type_constants.asm): ```diff const_def - -PHYSICAL EQU const_value const NORMAL const FIGHTING const FLYING const POISON const GROUND const ROCK const BIRD const BUG const GHOST const STEEL UNUSED_TYPES EQU const_value const_next 19 const CURSE_TYPE UNUSED_TYPES_END EQU const_value -SPECIAL EQU const_value const FIRE const WATER const GRASS const ELECTRIC const PSYCHIC_TYPE const ICE const DRAGON const DARK TYPES_END EQU const_value + +TYPE_MASK EQU %00111111 +PHYSICAL EQU %01000000 +SPECIAL EQU %10000000 +STATUS EQU %11000000 ``` We're going to store each move's type and category in the same byte. This works well for two reasons: 1. A byte has eight bits. Two bits can store four values, which is enough for the three categories; and the remaining six can store 64 values, which is more than enough for all the types, even with those unused middle ones. We'll just have to be careful to mask out the category bits when dealing with types alone. 2. Throughout the code, moves' categories are checked by comparing their type value with the `SPECIAL` constant; values less than it are physical, otherwise they're special. We're keeping `PHYSICAL` < `SPECIAL`, so those checks will all still work. The category bits are higher than the type bits, so the type won't interfere with this relation; every `SPECIAL` + type combination will be greater than every `PHYSICAL` + type one. ## 2. Update moves with their categories Edit [data/moves/moves.asm](../blob/master/data/moves/moves.asm): ```diff move: MACRO db \1 ; animation db \2 ; effect db \3 ; power - db \4 ; type - db \5 percent ; accuracy - db \6 ; pp - db \7 percent ; effect chance + db \4 | \5 ; type + db \6 percent ; accuracy + db \7 ; pp + db \8 percent ; effect chance ENDM Moves: ; entries correspond to constants/move_constants.asm - move POUND, EFFECT_NORMAL_HIT, 40, NORMAL, 100, 35, 0 - ... - move BEAT_UP, EFFECT_BEAT_UP, 10, DARK, 100, 10, 0 + move POUND, EFFECT_NORMAL_HIT, 40, NORMAL, PHYSICAL, 100, 35, 0 + ... + move BEAT_UP, EFFECT_BEAT_UP, 10, DARK, PHYSICAL, 100, 10, 0 ``` You'll have to assign the right category—`PHYSICAL`, `SPECIAL`, or `STATUS`—to all 251 moves, right after their types. There's a file which already does this with the default pokecrystal moves [here](https://pastebin.com/ZnX1imv9). (Note: that file also changes Curse's type from `CURSE_T` to `GHOST`.) ## 3. Mask out the category in `PrintMoveType` Edit [engine/pokemon/types.asm](../blob/master/engine/pokemon/types.asm): ```diff PrintMoveType: ; Print the type of move b at hl. push hl ld a, b dec a ld bc, MOVE_LENGTH ld hl, Moves call AddNTimes ld de, wStringBuffer1 ld a, BANK(Moves) call FarCopyBytes ld a, [wStringBuffer1 + MOVE_TYPE] + and TYPE_MASK pop hl ``` This is the first of many times we'll have to add `and TYPE_MASK` somewhere. ## 4. Mask out the category in nine places in the battle engine First, edit [engine/battle/effect_commands.asm](../blob/master/engine/battle/effect_commands.asm). There are *five* places in the code where we need to do this: ```diff ld a, BATTLE_VARS_MOVE_TYPE call GetBattleVar + and TYPE_MASK ``` 1. `BattleCommand_Stab` (actually, this usage calls `GetBattleVarAddr` instead of `GetBattleVar`) 2. `BattleCommand_Stab` again (although this one calls `GetBattleVar`) 3. `CheckTypeMatchup` 4. `BattleCommand_DamageCalc` 5. `CheckMoveTypeMatchesTarget` Just use your text editor to find all five occurrences of "`BATTLE_VARS_MOVE_TYPE`" in the file, and make that one-line change to all of them. Next, edit [engine/battle/move_effects/conversion.asm](../blob/master/engine/battle/move_effects/conversion.asm): ```diff ld hl, Moves + MOVE_TYPE call GetMoveAttr + and TYPE_MASK ``` Edit [engine/battle/move_effects/conversion2.asm](../blob/master/engine/battle/move_effects/conversion2.asm): ```diff ld hl, Moves + MOVE_TYPE call GetMoveAttr + and TYPE_MASK ... ld a, BATTLE_VARS_MOVE_TYPE call GetBattleVarAddr + and TYPE_MASK ``` And edit [engine/battle/move_effects/thunder.asm](../blob/master/engine/battle/move_effects/thunder.asm): ```diff ld a, BATTLE_VARS_MOVE_TYPE call GetBattleVarAddr + and TYPE_MASK ``` That's nine additions of `and TYPE_MASK` to mask out the category bits and leave only the type. ## 5. Make Hidden Power special Edit [engine/battle/hidden_power.asm](../blob/master/engine/battle/hidden_power.asm): ```diff ; Overwrite the current move type. push af ld a, BATTLE_VARS_MOVE_TYPE call GetBattleVarAddr pop af + or SPECIAL ld [hl], a ``` If you're using an older version of pokecrystal, you may also have to edit another line to make sure Hidden Power's type is calculated correctly: ```diff ; Skip unused types cp UNUSED_TYPES jr c, .done - add SPECIAL - UNUSED_TYPES + add UNUSED_TYPES_END - UNUSED_TYPES ``` ## 6. Update the AI to understand categories At this point the Physical/Special split *works*, technically, but for two things: the AI doesn't fully understand it, and the user interface doesn't show it. We'll take care of the AI first. Edit [engine/battle/ai/scoring.asm](../blob/master/engine/battle/ai/scoring.asm). There are a few unrelated changes to make here, so let's go over them one at a time. ```diff AI_Types: ... ; Discourage this move if there are any moves ; that do damage of a different type. push hl push de push bc ld a, [wEnemyMoveStruct + MOVE_TYPE] + and TYPE_MASK ld d, a ... call AIGetEnemyMove ld a, [wEnemyMoveStruct + MOVE_TYPE] + and TYPE_MASK cp d ``` Here we're just masking out categories again. ```diff AI_Smart_SpDefenseUp2: ... ; 80% chance to greatly encourage this move if -; enemy's Special Defense level is lower than +2, and the player is of a special type. +; enemy's Special Defense level is lower than +2, +; and the player's Pokémon is Special-oriented. cp BASE_STAT_LEVEL + 2 ret nc - ld a, [wBattleMonType1] - cp SPECIAL - jr nc, .encourage - ld a, [wBattleMonType2] - cp SPECIAL - ret c + push hl +; Get the pointer for the player's Pokémon's base Attack + ld a, [wBattleMonSpecies] + ld hl, BaseData + BASE_ATK + ld bc, BASE_DATA_SIZE + call AddNTimes +; Get the Pokémon's base Attack + ld a, BANK(BaseData) + call GetFarByte + ld d, a +; Get the pointer for the player's Pokémon's base Special Attack + ld bc, BASE_SAT - BASE_ATK + add hl, bc +; Get the Pokémon's base Special Attack + ld a, BANK(BaseData) + call GetFarByte + pop hl +; If its base Attack is greater than its base Special Attack, +; don't encourage this move. + cp d + ret c .encourage call AI_80_20 ret c dec [hl] dec [hl] ret ``` This routine used to encourage the AI to use moves that raise its Special Defense if the player's Pokémon was of a Special type. Since physical/special categories are now independent of types, we've changed it to check whether the player's base Special Attack is at least as high as its base Attack. ```diff AI_Smart_Encore: ... push hl ld a, [wEnemyMoveStruct + MOVE_TYPE] + and TYPE_MASK ld hl, wEnemyMonType1 predef CheckTypeMatchup ``` Just masking out the category again. ```diff AI_Smart_Curse: ... ld a, [wBattleMonType1] cp GHOST jr z, .greatly_encourage - cp SPECIAL - ret nc - ld a, [wBattleMonType2] - cp SPECIAL - ret nc call AI_80_20 ret c dec [hl] dec [hl] ret ``` This routine used to discourage the AI from using Curse if the player's Pokémon was of a Special type (since Curse raises the user's Defense, which is useless against special attacks). That's no longer meaningful, but it's not worth checking the player's base stats again. ## 7. Support printing category names Create **data/types/category_names.asm**: ```diff +CategoryNames: + dw .Physical + dw .Special + dw .Status + +.Physical: db "PHYSICAL@" +.Special: db "SPECIAL@" +.Status: db "STATUS@" ``` Create **engine/pokemon/categories.asm**: ```diff +GetMoveCategoryName: +; Copy the category name of move b to wStringBuffer1. + + ld a, b + dec a + ld bc, MOVE_LENGTH + ld hl, Moves + MOVE_TYPE + call AddNTimes + ld a, BANK(Moves) + call GetFarByte + +; Mask out the type + and ~TYPE_MASK +; Shift the category bits into the range 0-2 + rlc a + rlc a + dec a + + ld hl, CategoryNames + ld e, a + ld d, 0 + add hl, de + add hl, de + ld a, [hli] + ld h, [hl] + ld l, a + ld de, wStringBuffer1 + ld bc, MOVE_NAME_LENGTH + jp CopyBytes + +INCLUDE "data/types/category_names.asm" ``` And edit [main.asm](../blob/master/main.asm): ```diff INCLUDE "engine/pokemon/types.asm" +INCLUDE "engine/pokemon/categories.asm" ``` This is based on the routines in [engine/pokemon/types.asm](../blob/master/engine/pokemon/types.asm). ## 8. Display categories in battle Edit [engine/battle/core.asm](../blob/master/engine/battle/core.asm): ```diff + callfar UpdateMoveData + ld a, [wPlayerMoveStruct + MOVE_ANIM] + ld b, a + farcall GetMoveCategoryName hlcoord 1, 9 - ld de, .Type + ld de, wStringBuffer1 call PlaceString - hlcoord 7, 11 + ld h, b + ld l, c ld [hl], "/" - callfar UpdateMoveData ld a, [wPlayerMoveStruct + MOVE_ANIM] ld b, a hlcoord 2, 10 predef PrintMoveType .done ret .Disabled: db "Disabled!@" -.Type: - db "TYPE/@" ``` Instead of printing "TYPE/" in the move property box, we print the move's category. ## 9. Display categories in the Move screen Edit [engine/pokemon/mon_menu.asm](../blob/master/engine/pokemon/mon_menu.asm) (or [engine/menus/start_menu.asm](../blob/master/engine/menus/start_menu.asm) in older versions of pokecrystal): ```diff PlaceMoveData: xor a ldh [hBGMapMode], a hlcoord 0, 10 ld de, String_MoveType_Top call PlaceString hlcoord 0, 11 ld de, String_MoveType_Bottom call PlaceString hlcoord 12, 12 ld de, String_MoveAtk call PlaceString + ld a, [wCurSpecies] + ld b, a + farcall GetMoveCategoryName + hlcoord 1, 11 + ld de, wStringBuffer1 + call PlaceString ld a, [wCurSpecies] ld b, a - hlcoord 2, 12 + hlcoord 1, 12 + ld [hl], "/" + inc hl predef PrintMoveType ... String_MoveType_Top: - db "┌─────┐@ + db "┌────────┐@" String_MoveType_Bottom: - db "│TYPE/└@" + db "│ └@" ``` ```diff .moving_move ld a, " " hlcoord 1, 11 - ld bc, 5 + ld bc, 8 call ByteFill hlcoord 1, 12 lb bc, 5, SCREEN_WIDTH - 2 call ClearBox ``` Again, instead of printing "TYPE/" in the move property box, we print the move's category. There's no room for the "/" after the category, so here it goes before the type. Now we're done! ![Screenshot](screenshots/physical-special-split.png)