diff options
-rw-r--r-- | Add-a-new-Pack-pocket.md | 2 | ||||
-rw-r--r-- | Allow-map-tiles-to-appear-above-sprites-(so-NPCs-can-walk-behind-tiles)-with-PRIORITY-colors.md | 38 | ||||
-rw-r--r-- | Physical-Special-split.md | 453 | ||||
-rw-r--r-- | Tutorials.md | 1 | ||||
-rw-r--r-- | screenshots/physical-special-split.png | bin | 0 -> 2240 bytes |
5 files changed, 478 insertions, 16 deletions
diff --git a/Add-a-new-Pack-pocket.md b/Add-a-new-Pack-pocket.md index 5417b03..c74a1b1 100644 --- a/Add-a-new-Pack-pocket.md +++ b/Add-a-new-Pack-pocket.md @@ -893,6 +893,8 @@ So, `jr` and `jp` both jump to the given label. But `jp` encodes the full two-by + jp .DisplayPocket ``` +(If you're adding more pockets, you might have more errors like this.) + We're finally done!  diff --git a/Allow-map-tiles-to-appear-above-sprites-(so-NPCs-can-walk-behind-tiles)-with-PRIORITY-colors.md b/Allow-map-tiles-to-appear-above-sprites-(so-NPCs-can-walk-behind-tiles)-with-PRIORITY-colors.md index a2e5748..49850f3 100644 --- a/Allow-map-tiles-to-appear-above-sprites-(so-NPCs-can-walk-behind-tiles)-with-PRIORITY-colors.md +++ b/Allow-map-tiles-to-appear-above-sprites-(so-NPCs-can-walk-behind-tiles)-with-PRIORITY-colors.md @@ -1,6 +1,21 @@ Usually on overworld maps, sprites go on top of tiles. But there are exceptions: grass tiles overlap you as you walk, and the popup signs with location names (made of tiles) appear above all the NPC sprites. -It's possible to allow *any* tile in a tileset to appear above sprites by extending the palette system. The key is in [constants/hardware_constants.asm](../blob/master/constants/hardware_constants.asm): +Is it possible to make *any* tile in a tileset appear above sprites? Yes. + + +## Contents + +1. [Some background information](#1-some-background-information) +2. [Define `PAL_BG_PRIORITY_*` constants](#2-define-pal_bg_priority_-constants) +3. [Use one byte per color for tileset palettes](#3-use-one-byte-per-color-for-tileset-palettes) +4. [Fix the skipped space in palette_map.asm files](#4-fix-the-skipped-space-in-palette_mapasm-files) +5. [Fix the bank overflow](#5-fix-the-bank-overflow) +6. [Correctly read the extended palette data](#6-correctly-read-the-extended-palette-data) + + +## 1. Some background information + +We can extend the tileset palette system to enable tiles above sprites. The key is in [constants/hardware_constants.asm](../blob/master/constants/hardware_constants.asm): ```asm ; OAM attribute flags @@ -19,19 +34,10 @@ Y_FLIP EQU 1 << OAM_Y_FLIP ; $40 PRIORITY EQU 1 << OAM_PRIORITY ; $80 ``` -Every tile on the screen has an attribute byte. The lowest three bits define the color, which is why there's only room for eight colors (from `PAL_BG_GRAY`, 0, to `PAL_BG_TEXT`, 7). The other bits control other properties. In particular, the high bit controls **tile priority**. So if the [gfx/tilesets/\*\_palette\_map.asm](../blob/master/gfx/tilesets/) files could define tiles' priority as well as color, you could make any tile have priority over sprites. - - -## Contents - -1. [Define `PAL_BG_PRIORITY_*` constants](#1-define-pal_bg_priority_-constants) -2. [Use one byte per color for tileset palettes](#2-use-one-byte-per-color-for-tileset-palettes) -3. [Fix the skipped space in palette_map.asm files](#3-fix-the-skipped-space-in-palette_mapasm-files) -4. [Fix the bank overflow](#4-fix-the-bank-overflow) -5. [Correctly read the extended palette data](#5-correctly-read-the-extended-palette-data) +Every tile on the screen has an **attribute** byte. The lowest three bits define the color, which is why there's only room for eight colors (from `PAL_BG_GRAY`, 0, to `PAL_BG_TEXT`, 7). The other bits control other properties. In particular, the high bit controls **tile priority**. So if the [gfx/tilesets/\*\_palette\_map.asm](../blob/master/gfx/tilesets/) files could define tiles' priority as well as color, you could make any tile have priority over sprites. -## 1. Define `PAL_BG_PRIORITY_*` constants +## 2. Define `PAL_BG_PRIORITY_*` constants Edit [constants/tileset_constants.asm](../blob/master/constants/tileset_constants.asm): @@ -64,7 +70,7 @@ Edit [constants/tileset_constants.asm](../blob/master/constants/tileset_constant But we can't just start using colors like `PRIORITY_RED` in the tilesets' palette_map.asm files. The `tilepal` macro packs two tile color definitions into each byte, using four bits per tile: three for the color (`PALETTE_MASK`), one for the bank (`VRAM_BANK_1`). So we need to add space for the new priority data. -## 2. Use one byte per color for tileset palettes +## 3. Use one byte per color for tileset palettes Edit [gfx/tileset_palette_maps.asm](../blob/master/gfx/tileset_palette_maps.asm): @@ -84,7 +90,7 @@ Edit [gfx/tileset_palette_maps.asm](../blob/master/gfx/tileset_palette_maps.asm) ``` -## 3. Fix the skipped space in palette_map.asm files +## 4. Fix the skipped space in palette_map.asm files The [gfx/tilesets/\*\_palette\_map.asm](../blob/master/gfx/tilesets/) define tile palettes in order: first for tiles $00 to $5F, then for tiles $80 to $DF. Tiles $60 to $7F are skipped because those IDs are used for font graphics. But the skipping is done with a count of bytes, not of colors, so we need to double the counts. @@ -98,7 +104,7 @@ Edit each \_palette\_map.asm file: ``` -## 4. Fix the bank overflow +## 5. Fix the bank overflow Now the tileset palette data will take up twice as much space—one byte per tile instead of half a byte—so it won't fit in its ROM bank. Edit [main.asm](../blob/master/main.asm): @@ -124,7 +130,7 @@ Now the tileset palette data will take up twice as much space—one byte per til Since we don't specify a bank for "Tileset Palettes" in [pokecrystal.link](../blob/master/pokecrystal.link), rgblink will place it in any bank that has enough room. -## 5. Correctly read the extended palette data +## 6. Correctly read the extended palette data Edit [engine/tilesets/map_palettes.asm](../blob/master/engine/tilesets/map_palettes.asm): diff --git a/Physical-Special-split.md b/Physical-Special-split.md new file mode 100644 index 0000000..d9b4c58 --- /dev/null +++ b/Physical-Special-split.md @@ -0,0 +1,453 @@ +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. + + +## 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 TYPE_10 + const TYPE_11 + const TYPE_12 + const TYPE_13 + const TYPE_14 + const TYPE_15 + const TYPE_16 + const TYPE_17 + const TYPE_18 + const CURSE_T + UNUSED_TYPES_END EQU const_value + +-SPECIAL EQU const_value + const FIRE + const WATER + const GRASS + const ELECTRIC + const PSYCHIC + const ICE + const DRAGON + const DARK + TYPES_END EQU const_value ++ ++MOVE_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: ; 41afb +; 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://gitgud.io/pfero/axyllagame/blob/b545f49f30cef9c0d567e715cd58a123c6ac31c8/data/moves/moves.asm). (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: ; 5093a + ; 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 MOVE_TYPE_MASK + pop hl +``` + +This is the first of many times we'll have to add `and MOVE_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 MOVE_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 MOVE_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 MOVE_TYPE_MASK + ... + ld a, BATTLE_VARS_MOVE_TYPE + call GetBattleVarAddr ++ and MOVE_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 MOVE_TYPE_MASK +``` + +That's nine additions of `and MOVE_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 +``` + + +## 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: ; 38635 + ... + ; 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 MOVE_TYPE_MASK + ld d, a + ... + call AIGetEnemyMove + ld a, [wEnemyMoveStruct + MOVE_TYPE] ++ and MOVE_TYPE_MASK + cp d +``` + +Here we're just masking out categories again. + +```diff + AI_Smart_SpDefenseUp2: ; 38aed + + ... + + ; 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 $9 + ret nc + +- ld a, [wBattleMonType1] +- cp SPECIAL +- jr nc, .asm_38b09 +- 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 ++; If its base Attack is greater than its base Special Attack, ++; don't encourage this move. ++ cp d ++ jr nc, .asm_38b09 ++ pop hl ++ ret + + .asm_38b09 ++ pop hl + 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: ; 38c3b + ... + + push hl + ld a, [wEnemyMoveStruct + MOVE_TYPE] ++ and MOVE_TYPE_MASK + ld hl, wEnemyMonType1 + predef CheckTypeMatchup +``` + +Just masking out the category again. + +```diff + AI_Smart_Curse: ; 38e5c + ... + + ld a, [wBattleMonType1] + cp GHOST + jr z, .asm_38e92 +- 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 $ff ^ MOVE_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 + ; 3e74f + + .Disabled: + db "Disabled!@" +-.Type: +- db "TYPE/@" + ; 3e75f +``` + +Instead of printing "TYPE/" in the move property box, we print the move's category. + + +## 9. Display categories in the Move screen + +Edit [engine/menus/start_menu.asm](../blob/master/engine/menus/start_menu.asm): + +```diff + PlaceMoveData: ; 13256 + xor a + ld [hBGMapMode], a + hlcoord 0, 10 + ld de, String_MoveType_Top + call PlaceString + hlcoord 0, 11 + ld de, String_MoveType_Bottom + call PlaceString ++ ld a, [wCurMove] ++ ld b, a ++ farcall GetMoveCategoryName ++ hlcoord 1, 11 ++ ld de, wStringBuffer1 ++ call PlaceString + hlcoord 12, 12 + ld de, String_MoveAtk + call PlaceString + ld a, [wCurMove] + ld b, a +- hlcoord 2, 12 ++ hlcoord 1, 12 ++ ld [hl], "/" ++ inc hl + predef PrintMoveType + ... + + String_MoveType_Top: ; 132ba +- db "┌─────┐@ ++ db "┌────────┐@" + ; 132c2 + String_MoveType_Bottom: ; 132c2 +- db "│TYPE/└@" ++ db "│ └@" + ; 132ca +``` + +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! + + diff --git a/Tutorials.md b/Tutorials.md index a8d7fda..2a9ed62 100644 --- a/Tutorials.md +++ b/Tutorials.md @@ -29,4 +29,5 @@ Tutorials may use diff syntax to show edits: **Features from later generations:** +- [Physical/Special split](Physical-Special-split) - [Automatically reuse Repel](Automatically-reuse-Repel) diff --git a/screenshots/physical-special-split.png b/screenshots/physical-special-split.png Binary files differnew file mode 100644 index 0000000..1dd0923 --- /dev/null +++ b/screenshots/physical-special-split.png |