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.

Contents

  1. How item constants work
  2. Essential item data: name, description, attributes, and effect
  3. Examples
  4. Adding up to 254 items

1. How item constants work

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 to ITEM_BE, including 23 unused ITEM_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 to TM_NIGHTMARE. These are defined with an add_tm macro that simultaneously defines the next TM_MOVENAME item constant and the next MOVENAME_TMNUM constant (starting at 1 for the first add_tm use). The first time add_tm is used it defines TM01 as the current item, and any items after that are assumed to also be TMs. Note that ITEM_C3 and ITEM_DC are two unusable item IDs in-between the TMs—they can't become normal items, since they're greater than TM01, 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 to HM_WATERFALL. These are defined with an add_hm macro that simultaneously defines the next HM_MOVENAME item constant and the next MOVENAME_TMNUM constant (continuing from the TMs, so for instance, CUT_TMNUM is 51). The first time add_hm is used it defines HM01 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 in wScriptVar 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_tms and add_hms, there are also some add_mts. 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.

2. Essential item data: name, description, attributes, and effect

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. The HELD_* 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, or CANT_SELECT | CANT_TOSS. Most items have CANT_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, or TM_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.

3. Examples

No effect: Big Nugget

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:

Screenshot

RestoreHPEffect: Sweet Heart

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.

Screenshot

StatusHealingEffect: Lava Cookie

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.

Screenshot

PokeBallEffect: Dusk Ball

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.

Screenshot

EvoStoneEffect: Mist Stone

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.)

Screenshot

XItemEffect: X Sp.Def

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.

Screenshot

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.

TownMapEffect: Town Map

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.

Screenshot

Held effect: Eviolite

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.

Screenshot

4. Adding up to 254 items

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).

⚠️ **GitHub.com Fallback** ⚠️