This tutorial is for how to add a new move, allowing up to 255 moves. As an example, we'll add Nasty Plot. ## Contents 1. [Define a move constant](#1-define-a-move-constant) 2. [Give it a name and description](#2-give-it-a-name-and-description) 3. [Define its battle properties](#3-define-its-battle-properties) 4. [Define its animation](#4-define-its-animation) 5. [Let Pokémon learn the move](#5-let-pokémon-learn-the-move) 6. [Adding a 255th move](#6-adding-a-255th-move) 1. [Prepare move $FF](#61-prepare-move-ff) 2. [Swap move $FF with Struggle](#62-swap-move-ff-with-struggle) 3. [Remove Struggle from the `MetronomeExcepts` list](#63-remove-struggle-from-the-metronomeexcepts-list) 4. [Get rid of `NUM_ATTACKS + 1` checks](#64-get-rid-of-num_attacks--1-checks) 5. [Use $00 instead of $FF for Pursuit's effect](#65-use-00-instead-of-ff-for-pursuits-effect) ## 1. Define a move constant Edit [constants/move_constants.asm](../blob/master/constants/move_constants.asm): ```diff ; move ids ; indexes for: ; - Moves (see data/moves/moves.asm) ; - MoveNames (see data/moves/names.asm) ; - MoveDescriptions (see data/moves/descriptions.asm) ; - BattleAnimations (see data/moves/animations.asm) const_def const NO_MOVE ; 00 const POUND ; 01 ... const BEAT_UP ; fb + const NASTY_PLOT ; fc NUM_ATTACKS EQU const_value - 1 ; Battle animations use the same constants as the moves up to this point const_next $ff const ANIM_SWEET_SCENT_2 ; ff ... ``` Move constants are actually a subset of battle animation constants. $01 to $FB are the 251 constants from `POUND` to `BEAT_UP`; then $FC, $FD, and $FE are unused; then starting at $FF, `ANIM_SWEET_SCENT_2` and above correspond to animations beyond the ones played for moves (throwing Poké Balls, showing confusion, etc). Anyway, those three unused values can all be used for new moves. ## 2. Give it a name and description Edit [data/moves/names.asm](../blob/master/data/moves/names.asm): ```diff MoveNames:: db "POUND@" ... db "BEAT UP@" + db "NASTY PLOT@" ``` A name can be up to 12 characters long, plus a "@" at the end. Now edit [data/moves/descriptions.asm](../blob/master/data/moves/descriptions.asm): ```diff MoveDescriptions:: ; entries correspond to move ids (see constants/move_constants.asm) dw PoundDescription ... dw BeatUpDescription - dw MoveFCDescription + dw NastyPlotDescription dw MoveFDDescription dw MoveFEDescription dw MoveFFDescription dw Move00Description -MoveFCDescription: MoveFDDescription: MoveFEDescription: MoveFFDescription: Move00Description: db "?@" ... BeatUpDescription: db "Party #MON join" next "in the attack.@" + +NastyPlotDescription: + db "Sharply increases" + next "user's SPCL.ATK.@" ``` A description has two lines, each with up to 18 characters, plus a "@" at the end. ## 3. Define its battle properties Edit [data/moves/moves.asm](../blob/master/data/moves/moves.asm): ```diff 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 NASTY_PLOT, EFFECT_SP_ATK_UP_2, 0, DARK, 100, 20, 0 ``` The `move` defines these properties: - **animation:** Which animation to play when using the move. Remember, constants like `POUND` correspond to moves but also to battle animations, as we'll see later. - **effect:** What effect the move has. Valid effects are in [constants/move_effect_constants.asm](../blob/master/constants/move_effect_constants.asm). Some exist that aren't used for any moves yet, like `EFFECT_SP_ATK_UP_2`. - **power:** The base power. 0 for non-damaging moves; 1 for moves that do damage but not with the standard formula, like Seismic Toss, Counter, or Magnitude. (The AI uses this property to distinguish damaging and non-damaging moves.) - **type:** The type. - **accuracy:** The accuracy, from 1 to 100. - **PP:** The PP, from 5 to 40. Sketch has 1 PP but it requires special-case code in some places; and 40 is the maximum because any more and PP Up could boost it out of bounds. (PP is stored in 6 bits, not a full byte, so cannot exceed 63.) - **effect chance:** The chances of an effect triggering. Not applicable for all effects. ## 4. Define its animation Edit [data/moves/animations.asm](../blob/master/data/moves/animations.asm): ```diff BattleAnimations:: ; entries correspond to constants/move_constants.asm dw BattleAnim_0 dw BattleAnim_Pound ... dw BattleAnim_BeatUp - dw BattleAnim_252 + dw BattleAnim_NastyPlot dw BattleAnim_253 dw BattleAnim_254 dw BattleAnim_SweetScent2 ; $100 dw BattleAnim_ThrowPokeBall ... BattleAnim_0: -BattleAnim_252: BattleAnim_253: BattleAnim_254: BattleAnim_MirrorMove: anim_ret ... +BattleAnim_NastyPlot: BattleAnim_PsychUp: anim_1gfx ANIM_GFX_STATUS anim_call BattleAnim_FollowEnemyFeet_0 anim_bgeffect ANIM_BG_1A, $0, $1, $20 anim_sound 0, 0, SFX_PSYBEAM anim_obj ANIM_OBJ_PSYCH_UP, 44, 88, $0 anim_obj ANIM_OBJ_PSYCH_UP, 44, 88, $10 anim_obj ANIM_OBJ_PSYCH_UP, 44, 88, $20 anim_obj ANIM_OBJ_PSYCH_UP, 44, 88, $30 anim_wait 64 anim_incbgeffect ANIM_BG_1A anim_call BattleAnim_ShowMon_0 anim_wait 16 anim_ret ``` Designing a new animation is beyond the scope of this tutorial. They require careful placement and timing of different elements, and the scripting system used to do this is [poorly understood](../blob/master/docs/battle_anim_commands.md). Here we're just reusing Psych Up's animation for Nasty Plot, since it looks appropriate. ## 5. Let Pokémon learn the move By now the move fully exists—it might show up with Metronome—but no Pokémon can use it. So add it to level-up learnsets in [data/pokemon/evos_attacks.asm](../blob/master/data/pokemon/evos_attacks.asm), egg move sets in [data/pokemon/egg_moves.asm](../blob/master/data/pokemon/egg_moves.asm), or NPC trainers' parties in [data/trainers/parties.asm](../blob/master/data/trainers/parties.asm) (see the [new Pokémon](Add-a-new-Pokémon) and [new trainer](Add-a-new-trainer-class) tutorials for help with that). Or add a new TM for it, following [the tutorial](Add-a-new-TM). I added `NASTY_PLOT` to these sets, based on their canon ones in later generations: - [data/pokemon/evos_attacks.asm](../blob/master/data/pokemon/evos_attacks.asm): Meowth, Persian, Drowzee, Hypno, Mew, Pichu, Aipom, Slowking, Girafarig, Houndour, Houndoom - [data/pokemon/egg_moves.asm](../blob/master/data/pokemon/egg_moves.asm): Zubat, Drowzee, Mr. Mime, Togepi, Misdreavus, Houndour, Smoochum - [data/trainers/parties.asm](../blob/master/data/trainers/parties.asm): Karen's Houndoom ![Screenshot](screenshots/nasty-plot.png) ## 6. Adding a 255th move It's pretty easy to replace the unused moves 252, 253, and 254 ($FC, $FD, and $FE) using the steps above. But move 255 ($FF) is trickier. The value $FF is often used as a special case in the code: it's the maximum value a single byte can have, it marks the end of lists, and using it like any other value can be difficult to impossible. (If you're wondering why so many lists end with `db -1`, not `db $ff`, that's because the byte $FF can be 255 or −1 depending on context, due to [two's complement](https://en.wikipedia.org/wiki/Two%27s_complement) arithmetic.) ### 6.1. Prepare move $FF Let's say the 255th move will be Fake Out. First, follow the steps as usual. Define `FAKE_OUT` after your move $FE; give it a name, description, and battle properties (`FAKE_OUT, EFFECT_FAKE_OUT, 40, NORMAL, 100, 10, 0`); give it an animation (it can share `BattleAnim_Tackle`); and add it to Pokémon learnsets. Remember, this time we're introducing a new constant, not replacing an old one. So you also need to change `const_next $ff` to `const_next $100` for the battle animation constants, or just remove the `const_next` since there's no longer a gap between moves and battle animations. This means `ANIM_SWEET_SCENT_2` will be shifted from $FF to $100; `ANIM_THROW_POKE_BALL` from $100 to $101; and so on. ### 6.2. Swap move $FF with Struggle Because $FF (aka 255, aka −1) is treated specially, it's not actually suitable as a move ID. Luckily, Struggle is a move that's treated specially itself: no Pokémon can learn it naturally. So go back to all the files you edited in the previous step, and swap the lines for Struggle ($A5) with the lines for Fake Out ($FF). ### 6.3. Remove Struggle from the `MetronomeExcepts` list Now that Struggle is move $FF, including it in a list would end the list early. Usually moves would show up in various lists, but since Struggle is not a typical learnable move, it only shows up in one: `MetronomeExcepts`, the list of moves Metronome cannot copy. Edit [data/moves/metronome_exception_moves.asm](../blob/master/data/moves/metronome_exception_moves.asm): ```diff MetronomeExcepts: db NO_MOVE db METRONOME - db STRUGGLE db SKETCH db MIMIC db COUNTER db MIRROR_COAT db PROTECT db DETECT db ENDURE db DESTINY_BOND db SLEEP_TALK db THIEF db -1 ``` We still don't want Metronome to copy Struggle, so edit [engine/battle/move_effects/metronome.asm](../blob/master/engine/battle/move_effects/metronome.asm): ```diff ; No invalid moves. cp NUM_ATTACKS + 1 jr nc, .GetMove +; No Struggle. + cp STRUGGLE + jr z, .GetMove + ; None of the moves in MetronomeExcepts. push af ld de, 1 ld hl, MetronomeExcepts call IsInArray pop bc jr c, .GetMove ``` ### 6.4. Get rid of `NUM_ATTACKS + 1` checks Some places in the code do `cp NUM_ATTACKS + 1` to check if a move ID is not too high. If we have 255 moves, then `NUM_ATTACKS` + 1 will be 256 ($100), which won't fit in one byte, so these checks will cause a build error. However, the checks will also be redundant since every move ID is valid, so we can remove them. Edit [engine/battle/move_effects/metronome.asm](../blob/master/engine/battle/move_effects/metronome.asm) again: ```diff -; No invalid moves. - cp NUM_ATTACKS + 1 - jr nc, .GetMove ``` Edit [engine/events/battle_tower/battle_tower.asm](../blob/master/engine/events/battle_tower/battle_tower.asm): ```diff ValidateBTParty: ; Check for and fix errors in party data ... .dont_load ld [wCurPartyLevel], a ld hl, MON_MOVES add hl, bc ld d, NUM_MOVES - 1 ld a, [hli] and a - jr z, .not_move - cp NUM_ATTACKS + 1 - jr nc, .not_move - jr .valid_move + jr nz, .valid_move -.not_move dec hl ld a, POUND ld [hli], a xor a ld [hli], a ld [hli], a ld [hl], a jr .done_moves .valid_move - ld a, [hl] - cp NUM_ATTACKS + 1 - jr c, .next - ld [hl], $0 - -.next - inc hl + ld a, [hli] dec d jr nz, .valid_move ``` Edit [engine/pokemon/correct_party_errors.asm](../blob/master/engine/pokemon/correct_party_errors.asm): ```diff ld hl, wPartyMon1Moves ld a, [wPartyCount] ld b, a .loop5 push hl ld c, NUM_MOVES ld a, [hl] and a - jr z, .invalid_move - cp NUM_ATTACKS + 1 - jr c, .moves_loop + jr nz, .moves_loop -.invalid_move ld [hl], POUND .moves_loop ld a, [hl] and a - jr z, .fill_invalid_moves - cp NUM_ATTACKS + 1 - jr c, .next_move + jr nz, .next_move .fill_invalid_moves ... ``` And edit [mobile/mobile_5c.asm](../blob/master/mobile/mobile_5c.asm): ```diff CheckBTMonMovesForErrors: ld c, BATTLETOWER_PARTY_LENGTH ld hl, wBT_OTTempMon1Moves .loop push hl - ld a, [hl] - cp NUM_ATTACKS + 1 - jr c, .okay - ld a, POUND - ld [hl], a - -.okay - inc hl + ld a, [hli] ld b, NUM_MOVES - 1 .loop2 ld a, [hl] and a - jr z, .loop3 - cp NUM_ATTACKS + 1 - jr c, .next + jr nz, .next .loop3 ... ``` ### 6.5. Use $00 instead of $FF for Pursuit's effect There's one more way that $FF as a move ID is special. When Pursuit attacks a Pokémon that's switching out, the value $FF is used to end that attack early. Now that $FF is a valid move, we'll need to use a different value; `NO_MOVE` ($00) works. Edit [engine/battle/core.asm](../blob/master/engine/battle/core.asm): ```diff PursuitSwitch: ... ld a, BATTLE_VARS_MOVE call GetBattleVarAddr - ld a, $ff + xor a ; NO_MOVE ld [hl], a ... ``` And edit [engine/battle/effect_commands.asm](../blob/master/engine/battle/effect_commands.asm): ```diff CheckTurn: BattleCommand_CheckTurn: ; checkturn ; Repurposed as hardcoded turn handling. Useless as a command. -; Move $ff immediately ends the turn. +; NO_MOVE immediately ends the turn. ld a, BATTLE_VARS_MOVE call GetBattleVar - inc a + and a ; NO_MOVE? jp z, EndTurn ... ``` Now you can have 255 moves, as long as the last one is Struggle!