Add a new item - pret/pokecrystal GitHub Wiki
This tutorial is for how to add different kinds of new items, including a healing item, Poké Ball, evolution stone, key item, and held item.
- How item constants work
- Essential item data: name, description, attributes, and effect
- Examples
- Adding up to 254 items
Item constants are defined in constants/item_constants.asm. But unlike some constants, they're not a simple sequence where you can append new items to the end.
-
NO_ITEM
is the first item ID, which is used to indicate a lack of item. Leave this alone. - Item constants start out as an ordinary sequence, from
MASTER_BALL
toITEM_BE
, including 23 unusedITEM_XX
constants. Those are simplest to replace with any kind of item you want, except for TMs or HMs. - After the ordinary items come the TMs, from
TM_DYNAMICPUNCH
toTM_NIGHTMARE
. These are defined with anadd_tm
macro that simultaneously defines the nextTM_MOVENAME
item constant and the nextMOVENAME_TMNUM
constant (starting at 1 for the firstadd_tm
use). The first timeadd_tm
is used it definesTM01
as the current item, and any items after that are assumed to also be TMs. Note thatITEM_C3
andITEM_DC
are two unusable item IDs in-between the TMs—they can't become normal items, since they're greater thanTM01
, and some TM-related code manually skips over those two slots—so if you want to use them you'll have to correct that design flaw. - After the TMs come the HMs, from
HM_CUT
toHM_WATERFALL
. These are defined with anadd_hm
macro that simultaneously defines the nextHM_MOVENAME
item constant and the nextMOVENAME_TMNUM
constant (continuing from the TMs, so for instance,CUT_TMNUM
is 51). The first timeadd_hm
is used it definesHM01
as the current item, and any items after that are assumed to also be HMs, with all the differences that implies (they'll have an "H" in the Pack, using them will print "Booted up an HM", they can't be tossed, etc). - There can't be useful items after the HMs, since by definition anything after
HM01
is treated as an HM itself. In fact,ITEM_FA
is defined after the HMs, so you might need to move or delete it. -
ITEM_FROM_MEM
is the last item ID, which is used in some scripts to give whatever item is inwScriptVar
instead of a hard-coded item. (For instance, fruit trees rely on it so they don't need a separate script for each kind of Berry and Apricorn.)
(Note that after the add_tm
s and add_hm
s, there are also some add_mt
s. This macro defines more MOVENAME_TMNUM
constants, but not more TM_MOVENAME
or HM_MOVENAME
item constants, so the moves are recognized as teachable but don't have an associated item.)
It's important to understand that there's nothing special about the specific numeric values, except $00 for NO_ITEM
and $FF for ITEM_FROM_MEM
. const_def
starts a new constant sequence at 0, and every const
, add_tm
, or add_hm
after that defines the next constant in the sequence.
You may have already guessed this from the comment at the top of constants/item_constants.asm:
; item ids
; indexes for:
; - ItemNames (see data/items/names.asm)
; - ItemDescriptions (see data/items/descriptions.asm)
; - ItemAttributes (see data/items/attributes.asm)
; - ItemEffects (see engine/items/item_effects.asm)
Every kind of item has these pieces of data. ItemNames
, ItemDescriptions
, ItemAttributes
, and ItemEffects
are four tables with an entry for each item constant, one after another. It's important for them all to be in sync, so that the Nth constant correctly matches the Nth name, the Nth description, and so on.
Names are defined in data/items/names.asm. They're simply written as li "NAME"
: an opening quote, the name and a closing quote. Names can be up to 12 characters long. The number of printed characters also matters, since screens like the Pack and the Mart menus are a fixed width. For example, when the name "# DOLL"
gets printed as "POKé DOLL", that's 9 characters, not 6.
Descriptions are defined in data/items/descriptions.asm. Unlike with names, each entry in the table of descriptions is a pointer to the actual description data. Descriptions have two lines, each with up to 18 characters. Again, what matters is the printed length, since they have to fit in a text box of that size.
Attributes are defined in data/items/attributes.asm. They use an item_attribute
macro that defines many pieces of data at once:
- price: The price in Marts. Prices range from ¥0 to ¥65535 ($FFFF, the maximum value of a two-byte quantity). Items can be resold for half the price.
-
held effect: The effect when held by a Pokémon. Usually
HELD_NONE
, except for items like Leftovers. TheHELD_*
constants are defined in constants/item_data_constants.asm. We'll see how to create a new held effect later, for Eviolite. -
parameter: A piece of extra data associated with the item. Usually 0, but some item effects use this parameter. For instance, BrightPowder's parameter of 20 is interpreted by
BattleCommand_CheckHit
as an extra 20/256 (~7.8%) chance to miss a move. -
property: Controls whether you can select and/or toss the item.
NO_LIMITS
,CANT_SELECT
,CANT_TOSS
, orCANT_SELECT | CANT_TOSS
. Most items haveCANT_SELECT
, except for the usefully selectable ones like Bicycle or ItemFinder.CANT_TOSS
is for key items and HMs. -
pocket: Which pocket it goes in.
ITEM
,KEY_ITEM
,BALL
, orTM_HM
. -
field menu: What the item does when you try to use it in the field (i.e. outside of battle).
-
ITEMMENU_NOUSE
can't be used at all ("OAK: PLAYER! This isn't the time to use that!") -
ITEMMENU_CURRENT
just uses it right away (like Repel or Coin Case) -
ITEMMENU_PARTY
lets you choose a party Pokémon to use it on (like Potion or HP Up) -
ITEMMENU_CLOSE
has a confirmation menu before using it (like Bicycle or Escape Rope)
-
-
battle menu: What the item does when you try to use it during battle. Another
ITEMMENU_*
constant.
Effects are defined in engine/items/item_effects.asm. Like descriptions, there's a table of pointers to the effects themselves, and the pointers are what correspond directly with item constants. Effects are implemented in assembly code, and are virtually unlimited in what they can do. But many of them are flexible. Effects like StatusHealingEffect
, PokeBallEffect
, or EvoStoneEffect
get reused for many different items, and the items' particular details come from their attributes or from extra data tables related to that kind of item. Next we'll see how to add new items that use some of these general-purpose effects.
To start with, we'll implement a new item that just has the essential data. A Big Nugget doesn't do anything, it just has a high price. So here's how to add one:
Edit constants/item_constants.asm:
const WATER_STONE ; 18
- const ITEM_19 ; 19
+ const BIG_NUGGET ; 19
const HP_UP ; 1a
Edit data/items/names.asm:
li "WATER STONE"
- li "TERU-SAMA"
+ li "BIG NUGGET"
li "HP UP"
Edit data/items/descriptions.asm:
dw WaterStoneDesc
- dw TeruSama2Desc
+ dw BigNuggetDesc
dw HPUpDesc
...
-TeruSama2Desc:
- db "?@"
+BigNuggetDesc:
+ db "Made of pure gold."
+ next "Sell very high.@"
Edit data/items/attributes.asm:
-; ITEM_19
- item_attribute $9999, HELD_NONE, 0, NO_LIMITS, ITEM, ITEMMENU_NOUSE, ITEMMENU_NOUSE
+; BIG_NUGGET
+ item_attribute 20000, HELD_NONE, 0, CANT_SELECT, ITEM, ITEMMENU_NOUSE, ITEMMENU_NOUSE
Edit engine/items/item_effects.asm:
dw EvoStoneEffect ; WATER_STONE
- dw NoEffect ; ITEM_19
+ dw NoEffect ; BIG_NUGGET
dw VitaminEffect ; HP_UP
And last, edit data/items/catch_rate_items.asm:
TimeCapsule_CatchRateItems:
- db ITEM_19, LEFTOVERS
...
This is an easy-to-miss edit that's needed because of the Time Capsule. When a Pokémon is traded via Time Capsule from RBY to GSC, the byte in its data structure that represents its catch rate is interpreted as its held item. (A catch rate byte no longer exists in GSC, since that's part of base data.) For example, Jigglypuff's catch rate is 170, or $AA, so it holds a Polkadot Bow when traded to GSC because POLKADOT_BOW
is $AA.
The TimeCapsule_CatchRateItems
is used to convert catch rate values that don't correspond to valid items. So a Pokémon with a catch rate of $19, when traded from RBY to GSC, would hold Leftovers instead of ITEM_19
. (And even without any trading, if a Pokémon's held item is ITEM_19
, it will appear as "LEFTOVERS" in the status screen!) But since we're replacing ITEM_19
with a valid item, we don't want this to happen any more.
Anyway, that's all you need to add a basic item:
A Sweet Heart, from Gen 5, restores 20 HP, just like RageCandyBar. It can be used on the field or during battle, and enemy trainer AI should know how to use one too.
First, add the essential data. Replace ITEM_2D
with SWEET_HEART
; give it a name, description, and attributes (100, HELD_NONE, 0, CANT_SELECT, ITEM, ITEMMENU_PARTY, ITEMMENU_PARTY
); give it the RestoreHPEffect
; and remove ITEM_2D
from TimeCapsule_CatchRateItems
.
Then it's time to implement the specific HP healing amount for Sweet Heart. Notice how most healing items—Potion, Fresh Water, etc—have an item_attribute
parameter value equal to how much they heal? Those aren't actually used anywhere. Instead, the healing amounts for items used by the player, by the enemy trainer, and (when possible) by the Pokémon holding it are all handled separately.
Edit data/items/heal_hp.asm:
HealingHPAmounts:
dbw FRESH_WATER, 50
...
dbw BERRY_JUICE, 20
+ dbw SWEET_HEART, 20
dbw -1, 0 ; end
HealingHPAmounts
is used by GetHealingItemAmount
, which is called by ItemRestoreHP
, which is called by RestoreHPEffect
, which is the effect we already assigned to Sweet Heart; so now it will successfully heal 20 HP when used by the player.
Now edit engine/battle/ai/items.asm:
AI_Items:
dbw FULL_RESTORE, .FullRestore
...
dbw X_SPECIAL, .XSpecial
+ dbw SWEET_HEART, .SweetHeart
db -1 ; end
...
.Potion:
call .HealItem
jp c, .DontUse
ld b, 20
call EnemyUsedPotion
jp .Use
+
+.SweetHeart:
+ call .HealItem
+ jp c, .DontUse
+ ld b, 20
+ call EnemyUsedSweetHeart
+ jp .Use
...
EnemyUsedPotion:
ld a, POTION
ld b, 20
jr EnemyPotionContinue
+
+EnemyUsedSweetHeart:
+ ld a, SWEET_HEART
+ ld b, 20
+ jr EnemyPotionContinue
Now if a trainer has a Sweet Heart (as defined in data/trainers/attributes.asm) they'll be able to use it. The .HealItem
subroutine called by .SweetHeart
determines whether the healing item will be used, with different random chances when the enemy has half or a quarter of their HP left; and the EnemyPotionContinue
routine does the whole process of restoring HP, animating the HP bar, playing the sound effect, etc.
A Lava Cookie, from Gen 3, heals any status condition, just like Full Heal. It can be used on the field or during battle, and enemy trainer AI should know how to use one too.
First, add the essential data. Replace ITEM_32
with LAVA_COOKIE
; give it a name, description, and attributes (200, HELD_NONE, 0, CANT_SELECT, ITEM, ITEMMENU_PARTY, ITEMMENU_PARTY
); give it the StatusHealingEffect
; and remove ITEM_32
from TimeCapsule_CatchRateItems
.
Then it's time to implement the specific status-healing effect. Edit data/items/heal_status.asm:
StatusHealingActions:
; item, party menu action text, status
db ANTIDOTE, PARTYMENUTEXT_HEAL_PSN, 1 << PSN
...
db MIRACLEBERRY, PARTYMENUTEXT_HEAL_ALL, %11111111
+ db LAVA_COOKIE, PARTYMENUTEXT_HEAL_ALL, %11111111
db -1, 0, 0 ; end
%11111111
is a bit mask for all status conditions, but that particular value is checked for by HealStatus
to allow healing confusion as well. A different value, like 1 << PSN
or (1 << BRN) | (1 << FRZ)
, could be used to heal only certain conditions.
StatusHealingActions
is used by GetItemHealingAction
, which is called by UseStatusHealer
, which is called by StatusHealingEffect
, which is the effect we already assigned to Lava Cookie; so now it will successfully heal all status conditions when used by the player.
Now edit engine/battle/ai/items.asm:
AI_Items:
dbw FULL_RESTORE, .FullRestore
...
dbw X_SPECIAL, .XSpecial
+ dbw LAVA_COOKIE, .LavaCookie
db -1 ; end
.FullHeal:
call .Status
jp c, .DontUse
call EnemyUsedFullHeal
jp .Use
+
+.LavaCookie:
+ call .Status
+ jp c, .DontUse
+ call EnemyUsedLavaCookie
+ jp .Use
...
EnemyUsedFullHeal:
call AIUsedItemSound
call AI_HealStatus
ld a, FULL_HEAL
jp PrintText_UsedItemOn_AND_AIUpdateHUD
+
+EnemyUsedLavaCookie:
+ call AIUsedItemSound
+ call AI_HealStatus
+ ld a, LAVA_COOKIE
+ jp PrintText_UsedItemOn_AND_AIUpdateHUD
Now if a trainer has a Lava Cookie (as defined in data/trainers/attributes.asm) they'll be able to use it. The .Status
subroutine called by .LavaCookie
determines whether the healing item will be used, with different random chances depending on the enemy AI and the battle situation; and the EnemyUsedLavaCookie
routine does the whole process of healing status, playing the sound effect, etc.
A Dusk Ball, from Gen 4, has a 3.5× catch rate if used in caves or at night.
First, add the essential data. Replace ITEM_5A
with DUSK_BALL
; give it a name, description, and attributes (1000, HELD_NONE, 0, CANT_SELECT, BALL, ITEMMENU_NOUSE, ITEMMENU_CLOSE
); give it the PokeBallEffect
; and remove ITEM_5A
from TimeCapsule_CatchRateItems
.
A piece of data specific to Poké Balls is what color they are when thrown in battle. Edit data/battle_anims/ball_colors.asm:
BallColors:
db MASTER_BALL, PAL_BATTLE_OB_GREEN
...
db LOVE_BALL, PAL_BATTLE_OB_RED
+ db DUSK_BALL, PAL_BATTLE_OB_GREEN
db -1, PAL_BATTLE_OB_GRAY
Then it's time to implement the specific Pokémon-catching effect. Edit engine/items/item_effects.asm again:
BallMultiplierFunctionTable:
; table of routines that increase or decrease the catch rate based on
; which ball is used in a certain situation.
dbw ULTRA_BALL, UltraBallMultiplier
...
dbw PARK_BALL, ParkBallMultiplier
+ dbw DUSK_BALL, DuskBallMultiplier
db -1 ; end
+DuskBallMultiplier:
+; is it night?
+ ld a, [wTimeOfDay]
+ cp NITE
+ jr z, .night_or_cave
+; or are we in a cave?
+ ld a, [wEnvironment]
+ cp CAVE
+ ret nz ; neither night nor cave
+
+.night_or_cave
+; b is the catch rate
+; a := b + b + b == b × 3
+ ld a, b
+ add a
+ jr c, .max
+
+ add b
+ jr c, .max
+
+ ld b, a
+ ret
+
+.max
+ ld b, $ff
+ ret
DuskBallMultiplier
, like all the Ball multiplier routines, just has to modify b
according to the relevant factors. If it's nighttime or we're in a cave, it multiplies b
by 3; otherwise it doesn't modify b
.
A Mist Stone is a rumored item from Gen 1 that evolves Pokémon into "PokéGods". A more realistic function would be to evolve Pokémon like Machoke or Haunter that otherwise require trading, which can be inconvenient.
First, add the essential data. Replace ITEM_64
with MIST_STONE
; give it a name, description, and attributes (2100, HELD_NONE, 0, CANT_SELECT, ITEM, ITEMMENU_PARTY, ITEMMENU_NOUSE
); give it the EvoStoneEffect
; and remove ITEM_64
from TimeCapsule_CatchRateItems
.
That's actually all you need to do for the item itself. Now as long as it's declared to evolve a Pokémon, it will do so correctly.
Edit data/pokemon/evos_attacks.asm:
KadabraEvosAttacks:
+ db EVOLVE_ITEM, MIST_STONE, ALAKAZAM
db EVOLVE_TRADE, -1, ALAKAZAM
db 0 ; no more evolutions
...
MachokeEvosAttacks:
+ db EVOLVE_ITEM, MIST_STONE, MACHAMP
db EVOLVE_TRADE, -1, MACHAMP
db 0 ; no more evolutions
...
GravelerEvosAttacks:
+ db EVOLVE_ITEM, MIST_STONE, GOLEM
db EVOLVE_TRADE, -1, GOLEM
db 0 ; no more evolutions
...
HaunterEvosAttacks:
+ db EVOLVE_ITEM, MIST_STONE, GENGAR
db EVOLVE_TRADE, -1, GENGAR
db 0 ; no more evolutions
...
(Note that if a Pokémon has too many evolution methods, they'll say "NOT ABLE" to evolve with an item even if they are. This is a known bug with PlacePartyMonEvoStoneCompatibility
and has a simple solution.)
X Sp.Def, from Gen 4, boosts Special Defense by one stage in battle. It can only be used during battle, and enemy trainer AI should know how to use one too.
(In Gen 1, X Special boosted the single Special stat. Gen 2 split Special into Attack and Defense, but X Special only boosted Special Attack, and it took until Gen 4 to split the item as well into X Sp.Atk and X Sp.Def.)
First, add the essential data. Replace ITEM_78
with X_SP_DEF
; give it a name, description, and attributes (350, HELD_NONE, 0, CANT_SELECT, ITEM, ITEMMENU_NOUSE, ITEMMENU_CLOSE
); give it the XItemEffect
; and remove ITEM_78
from TimeCapsule_CatchRateItems
.
A piece of data specific to X items is which stat they boost. Edit data/items/x_stats.asm:
XItemStats:
; item, stat
db X_ATTACK, ATTACK
db X_DEFEND, DEFENSE
db X_SPEED, SPEED
db X_SPECIAL, SP_ATTACK
+ db X_SP_DEF, SP_DEFENSE
XItemStats
is used by XItemEffect
, so now it will successfully boost Special Defense when used by the player.
Now edit engine/battle/ai/items.asm:
AI_Items:
dbw FULL_RESTORE, .FullRestore
...
dbw X_SPECIAL, .XSpecial
+ dbw X_SP_DEF, .XSpDef
db -1 ; end
...
.XSpecial:
call .XItem
jp c, .DontUse
call EnemyUsedXSpecial
jp .Use
+
+.XSpDef:
+ call .XItem
+ jp c, .DontUse
+ call EnemyUsedXSpDef
+ jp .Use
...
+EnemyUsedXSpDef:
+ ld b, SP_DEFENSE
+ ld a, X_SP_DEF
+ jr EnemyUsedXItem
EnemyUsedXSpecial:
ld b, SP_ATTACK
ld a, X_SPECIAL
; Parameter
; a = ITEM_CONSTANT
; b = BATTLE_CONSTANT (ATTACK, DEFENSE, SPEED, SP_ATTACK, SP_DEFENSE, ACCURACY, EVASION)
EnemyUsedXItem:
...
Now if a trainer has an X Sp.Def (as defined in data/trainers/attributes.asm) they'll be able to use it. The .XItem
subroutine called by .XSpDef
determines whether the healing item will be used, with different random chances depending on the enemy AI and the battle situation; and the EnemyUsedXSpDef
routine does the whole process of boosting the stat, playing the sound effect, etc.
Note that I named it "X SPCL.DEF", not "X SP.DEF", because that's the naming convention for stats in Gen 2. I also renamed "X SPECIAL" to "X SPCL.ATK" so they match.
The Town Map was present in Gen 1 but replaced in Gen 2 by the Pokégear's Map Card; however, it still exists in the code. Its TownMapEffect
implementation is buggy, but can be fixed.
First, add the essential data. The TOWN_MAP
item constant already exists, but it needs a proper name, description, and attributes (0, HELD_NONE, 0, CANT_SELECT | CANT_TOSS, KEY_ITEM, ITEMMENU_CURRENT, ITEMMENU_NOUSE
).
Now fix the effect. Edit engine/items/item_effects.asm:
TownMapEffect:
- farcall PokegearMap
+ call FadeToMenu
+ farcall _TownMap
+ call Call_ExitMenu
+ xor a
+ ldh [hBGMapMode], a
+ farcall Pack_InitGFX
+ farcall WaitBGMap_DrawPackGFX
+ farcall Pack_InitColors
ret
This code is based on how items with ITEMMENU_PARTY
work, as written in UseItem.Party
in engine/items/pack.asm.
Eviolite, from Gen 5, boosts Defense and Special Defense by 50% if held by a Pokémon that is not fully evolved.
First, add the essential data. Replace ITEM_87
with EVIOLITE
; give it a name, description, and attributes (200, HELD_EVIOLITE, 0, CANT_SELECT, ITEM, ITEMMENU_NOUSE, ITEMMENU_NOUSE
); give it NoEffect
(it has a held effect, but no used effect); and remove ITEM_87
from TimeCapsule_CatchRateItems
.
HELD_EVIOLITE
has not been defined yet, so we'll do that next. Edit constants/item_data_constants.asm:
const_next 40
const_skip
const_skip
const HELD_METAL_POWDER
+ const HELD_EVIOLITE
Two things to note about these HELD_*
constants. One, they're not a continuous sequence; Game Freak usually grouped related ones together, starting from round numbers like 40. This was probably just to make it easier to deal with the raw numeric values, using the less powerful development tools of the late 90s. Two, there's no data associated with these constants. They're just unique values that the battle engine can check for in any arbitrary circumstances. If one of them is unused you can always replace it with something useful.
In other words, adding an effect for HELD_EVIOLITE
won't involve updating any data tables; we'll just have to write some all-new assembly code. So if you want to design an original held item, you'll have to become familiar with how the battle engine works, and figure out which parts need updating to accomodate your item.
Eviolite's effect is pretty similar to Metal Powder, which boosts Ditto's defenses. Searching through the battle engine code for references to METAL_POWDER
finds that it's implemented as a DittoMetalPowder
routine, which gets called in two places, one for the player and one for the enemy. (Note that it checks directly for whether the held item is METAL_POWDER
, not whether the held item's effect is HELD_METAL_POWDER
. Either check would be okay, though.)
Anyway, edit engine/battle/effect_commands.asm:
DittoMetalPowder:
...
+UnevolvedEviolite:
+; get the defender's species
+ ld a, MON_SPECIES
+ call BattlePartyAttr
+ ldh a, [hBattleTurn]
+ and a
+ ld a, [hl]
+ jr nz, .got_species
+ ld a, [wTempEnemyMonSpecies]
+
+.got_species
+; check if the defender has any evolutions
+; hl := EvosAttacksPointers + (species - 1) * 2
+ dec a
+ push hl
+ push bc
+ ld c, a
+ ld b, 0
+ ld hl, EvosAttacksPointers
+ add hl, bc
+ add hl, bc
+; hl := the species' entry from EvosAttacksPointers
+ ld a, BANK(EvosAttacksPointers)
+ call GetFarWord
+; a := the first byte of the species' *EvosAttacks data
+ ld a, BANK("Evolutions and Attacks")
+ call GetFarByte
+; if a == 0, there are no evolutions, so don't boost stats
+ and a
+ pop bc
+ pop hl
+ ret z
+
+; check if the defender's item is Eviolite
+ push bc
+ call GetOpponentItem
+ ld a, b
+ cp HELD_EVIOLITE
+ pop bc
+ ret nz
+
+; boost the relevant defense stat in bc by 50%
+ ld a, c
+ srl a
+ add c
+ ld c, a
+ ret nc
+
+ srl b
+ ld a, b
+ and a
+ jr nz, .done
+ inc b
+.done
+ scf
+ rr c
+ ret
BattleCommand_DamageStats:
...
PlayerAttackDamage:
; Return move power d, player level e, enemy defense c and player attack b.
...
ld a, [wBattleMonLevel]
ld e, a
call DittoMetalPowder
+ call UnevolvedEviolite
ld a, 1
and a
ret
...
EnemyAttackDamage:
...
ld a, [wEnemyMonLevel]
ld e, a
call DittoMetalPowder
+ call UnevolvedEviolite
ld a, 1
and a
ret
The implementation of UnevolvedEviolite
is very similar to DittoMetalPowder
, except the simple check for whether the species is DITTO
has been replaced by a check for evolutions, similar to the check in MoonBallMultiplier
in engine/items/item_effects.asm. (Also, instead of checking whether [hl]
is EVIOLITE
, we check whether b
is HELD_EVIOLITE
; either would be fine, since GetOpponentItem
returns "the effect of the opponent's item in bc
, and its id at hl
", as explained in a comment.) bc
gets repeatedly saved on the stack with push
and pop
because it contains the defense stat, and we don't want the various pre-boost checks to corrupt the original value.
In general, if you're implementing a custom held item effect, think about which items have similar effects, and which other code might already do something like what you want.
Like most IDs in pokecrystal, item IDs are one byte each, so they can go from 0 to 255. Item $00 is NO_ITEM
and $FF (−1) is an end-of-list marker, which leaves 254 usable IDs.
We've been replacing unused items with useful ones, but in principle you can also add new items (as long as you're careful to arrange regular items before TMs, then HMs last). Only 251 item constants are defined, from NO_ITEM
to ITEM_FA
, but the tables of ItemNames
, ItemDescriptions
, and ItemAttributes
already have 256 entries each (and ItemEffects
has entries for every regular item; the TMs and HMs don't use effects). So just be careful to keep the constants and all those tables in sync. Remember that adding a new constant in the middle of a sequence will shift all the ones after it.
If make
gives you the error "Expression must be 8-bit", you're probably using an ID constant greater than 255, which won't fit in one byte (eight bits). Double-check the constants and make sure they're all between $00 and $FF. It's easy to forget about the useless ITEM_FA
at the very end, which can end up shifted beyond the 8-bit range if you add too many new items. Feel free to delete it, and remove its entry from TimeCapsule_CatchRateItems
(the only place ITEM_FA
is ever used).