Generation 6 Experience System - pret/pokecrystal GitHub Wiki

In Pokémon X/Y the experience system received some changes, giving 100% of the total experience earned in battle to all participant Pokémon. The Exp. Share was also reworked to be a Key Item that can be turned on or off, granting to all non-fainted Pokémon who didn't participate in battle 50% of the total experience individually.

The goal of this tutorial is to teach you how to upgrade the experience system to behave in a similar way.

NOTE: As a side effect, both participant and non-participant Pokémon will also receive 100% of the total Stat Experience (a.k.a Stat Exp., the predecessor of Effort Values in generation 2) whenever an enemy faints.

Without further ado, let's start!

Content

  1. Rework the Exp. Share item
    1. Create a variable to store toggle's state
    2. Make the Exp. Share a Key Item
    3. Replace prize in Lottery Corner
  2. Rework the experience system
    1. Change how experience is distributed after defeating an enemy
    2. Delete remaining Exp. Share item checks
    3. Delete unused labels in WRAM

1. Rework the Exp. Share item

Before tackling the experience system, we must first change how the Exp. Share item works. In the first part of the tutorial we'll transform it into a Key Item that, when turned on, will tell the responsible function to give 50% of total experience to non-participant Pokémon.

1. Create a variable to store toggle's state

Before modifying how the Exp. Share works, we have to create a WRAM label to store the current state of the toggle. For that, let's edit ram/wram.asm:

 wHallOfFameCount:: db
-	ds 1
+wExpShareToggle:: db
 wTradeFlags:: flag_array NUM_NPC_TRADES
 	ds 1
 wMooMooBerries:: db

The reason we chose this specific spot is because it's within a section which contains information that's preserved when you save your game; in other words, if we decide to reboot the game the toggle's state won't change from the last time we saved.

2. Make the Exp. Share a Key Item

The next step is to transform the Exp. Share into a Key Item that can be toggled when used, just like in generation 6 and 7. For this, we'll edit the item's attributes, description and effect when used. For this, we'll edit various files, starting with data/items/attributes.asm, giving it attributes similar to a Key Item:

 ; EXP_SHARE
-	item_attribute 3000, HELD_NONE, 0, CANT_SELECT, ITEM, ITEMMENU_NOUSE, ITEMMENU_NOUSE
+	item_attribute 0, HELD_NONE, 0, CANT_SELECT | CANT_TOSS, KEY_ITEM, ITEMMENU_CURRENT, ITEMMENU_NOUSE

NOTE: If you don't know how the attributes' values work or what they mean, please refer to this tutorial about adding a new item for more information.

The next file is data/items/descriptions.asm, where we'll update the item's description to clarify it's no longer holdable:

 ExpShareDesc:
 	db   "Shares battle EXP."
-	next "Points. (HOLD)@"
+	next "Points.@"

We also must edit engine/items/item_effects.asm, where it defines and assigns the item effects when used from the bag in the overworld/during battle. Let's create ExpShareEffect to update the Exp. Share's toggle value whenever is used, and give it to the Exp. Share item:

 ItemEffects:
 ; entries correspond to item ids (see constants/item_constants.asm)
 	table_width 2, ItemEffects
 	...
-	dw NoEffect            ; EXP_SHARE
+	dw ExpShareEffect      ; EXP_SHARE
 	...
 	assert_table_length ITEM_B3
 
 ...
 
 
-LoadHPIntoBuffer3: ; unreferenced
-	ld a, d
-	ld [wHPBuffer3 + 1], a
-	ld a, e
-	ld [wHPBuffer3], a
-	ret
-	
-LoadHPFromBuffer3: ; unreferenced
-	ld a, [wHPBuffer3 + 1]
-	ld d, a
-	ld a, [wHPBuffer3]
-	ld e, a
-	ret
 
+ExpShareEffect:
+	ld a, [wExpShareToggle]
+	xor 1
+	ld [wExpShareToggle], a
+	and a
+	ld hl, ExpShareToggleOn
+	jp nz, PrintText
+
+	ld hl, ExpShareToggleOff
+	jp PrintText
 
 ...
 
 
-ItemGotOnText: ; unreferenced
-	text_far _ItemGotOnText
-	text_end
 
-ItemGotOffText: ; unreferenced
-	text_far _ItemGotOffText
-	text_end
 
+ExpShareToggleOff:
+	text_far _ExpShareToggleOff
+	text_end
 
+ExpShareToggleOn:
+	text_far _ExpShareToggleOn
+	text_end

We deleted unreferenced functions to save space while at the same time adding our new effect alongside the text commands necessary to display text whenever it's used.

Speaking of text, let's go to data/text/common_3.asm and replace the now unused _ItemGotOnText and _ItemGotOffText with two new texts for when the Exp. Share is turned on/off:

-_ItemGotOnText::
-	text "<PLAYER> got on the@"
-	text_low
-	text_ram wStringBuffer2
-	text "."
-	prompt
-
-_ItemGotOffText::
-	text "<PLAYER> got off@"
-	text_low
-	text "the @"
-	text_ram wStringBuffer2
-	text "."
-	prompt

+_ExpShareToggleOn::
+	text "The EXP.SHARE was"
+	line "turned on."
+	prompt
+
+_ExpShareToggleOff::
+	text "The EXP.SHARE was"
+	line "turned off."
+	prompt

3. Replace prize in Lottery Corner

There are two instances in the game where we can get the Exp. Share item: when giving the Red Scale to Mr. Pokémon and as a 2nd place's prize from the Lottery Corner. Now that it's a Key Item, we only need to get it once, so we'll replace the Lottery Corner prize with a normal item.

In this example, we'll replace it with a Lucky Egg. Edit maps/RadioTower1F.asm:

 RadioTower1FLuckyNumberManScript:
 	...
 .SecondPlace:
 	writetext RadioTower1FLuckyNumberManOkayMatchText
 	playsound SFX_2ND_PLACE
 	waitsfx
 	promptbutton
-	giveitem EXP_SHARE
+	giveitem LUCKY_EGG
 	iffalse .BagFull
 	itemnotify
 	setflag ENGINE_LUCKY_NUMBER_SHOW
 	sjump .GameOver
 	
 ...
 
 RadioTower1FLuckyNumberManOkayMatchText:
 	text "Hey! You've"
 	line "matched the last"
 	cont "three numbers!"
 
 	para "You've won second"
-	line "prize, an EXP."
-	cont "SHARE!"
+	line "prize, a LUCKY"
+	cont "EGG!"
 	done

2. Rework the experience system

Our new Exp. Share is working, but the toggle itself isn't doing anything. In this second part of the tutorial we'll make it work and also rework the experience system to give 100% of the total experience to each participant Pokémon (and 50% experience to each non-participants if the Exp. Share is on).

1. Change how experience is distributed after defeating an enemy

We'll perform the first step in engine/battle/core.asm by editing how to handle the gained experience after defeating an enemy. For that let's go and edit UpdateBattleStateAndExperienceAfterEnemyFaint:

 UpdateBattleStateAndExperienceAfterEnemyFaint:
 	...
 .player_mon_did_not_faint
 	...
 	ld a, [wBattleResult]
 	and BATTLERESULT_BITMASK
 	ld [wBattleResult], a ; WIN
-	call IsAnyMonHoldingExpShare
-	jr z, .skip_exp
-	ld hl, wEnemyMonBaseStats
-	ld b, wEnemyMonEnd - wEnemyMonBaseStats
-.loop
-	srl [hl]
-	inc hl
-	dec b
-	jr nz, .loop
-
-.skip_exp
-	ld hl, wEnemyMonBaseStats
-	ld de, wBackupEnemyMonBaseStats
-	ld bc, wEnemyMonEnd - wEnemyMonBaseStats
-	call CopyBytes
-	xor a
-	ld [wGivingExperienceToExpShareHolders], a
-	call GiveExperiencePoints
-	call IsAnyMonHoldingExpShare
-	ret z
+	; Preserve bits of non-fainted participants
+	ld a, [wBattleParticipantsNotFainted]
+	ld d, a
+	push de
+	call GiveExperiencePoints
+	pop de
+	; If Exp. Share is ON, give 50% EXP to non-participants
+	ld a, [wExpShareToggle]
+	and a
+	ret z
+	ld hl, wEnemyMonBaseExp
+	srl [hl]
 	ld a, [wBattleParticipantsNotFainted]
 	push af
 	ld a, d
+	xor %00111111
 	ld [wBattleParticipantsNotFainted], a
-	ld hl, wBackupEnemyMonBaseStats
-	ld de, wEnemyMonBaseStats
-	ld bc, wEnemyMonEnd - wEnemyMonBaseStats
-	call CopyBytes
-	ld a, $1
-	ld [wGivingExperienceToExpShareHolders], a
 	call GiveExperiencePoints
 	pop af
 	ld [wBattleParticipantsNotFainted], a
 	ret

Originally the code used to check if at least one of our Pokémon had the Exp. Share equipped (done by IsAnyMonHoldingExpShare) and, if that was the case, halve both the total experience and the Stat Exp.; the first half was divided among participant Pokémon and the other half among Exp. Share holders.

Now the code will try to divide 100% of the total experience among each participant and, if the Exp. Share is on, halve the total experience and distribute it among non-participants. You might've also noticed that we're preserving the value of wBattleParticipantsNotFainted in register d, this is because it gets overwritten at some point by GiveExperiencePoints and we need the original value to invert its bits and give 50% of the experience to the Pokémon who didn't participate in battle.

There's still one thing, though. If you paid attention, I said the code still tries to divide the experience among participants and/or non-participants and we need to change this behavior. For this we can simply delete the call to .EvenlyDivideExpAmongParticipants in the same file:

 GiveExperiencePoints:
 ; Give experience.
 ; Don't give experience if linked or in the Battle Tower.
 	ld a, [wLinkMode]
 	and a
 	ret nz
 
 	ld a, [wInBattleTowerBattle]
 	bit 0, a
 	ret nz
 
-	call .EvenlyDivideExpAmongParticipants
 	xor a
 	ld [wCurPartyMon], a
 	ld bc, wPartyMon1Species
 	...

And since .EvenlyDivideExpAmongParticipants is now unused, we can also safely delete the local function, which is inside GiveExperiencePoints:

-.EvenlyDivideExpAmongParticipants:
-; count number of battle participants
-	ld a, [wBattleParticipantsNotFainted]
-	ld b, a
-	ld c, PARTY_LENGTH
-	ld d, 0
-.count_loop
-	xor a
-	srl b
-	adc d
-	ld d, a
-	dec c
-	jr nz, .count_loop
-	cp 2
-	ret c
-
-	ld [wTempByteValue], a
-	ld hl, wEnemyMonBaseStats
-	ld c, wEnemyMonEnd - wEnemyMonBaseStats
-.base_stat_division_loop
-	xor a
-	ldh [hDividend + 0], a
-	ld a, [hl]
-	ldh [hDividend + 1], a
-	ld a, [wTempByteValue]
-	ldh [hDivisor], a
-	ld b, 2
-	call Divide
-	ldh a, [hQuotient + 3]
-	ld [hli], a
-	dec c
-	jr nz, .base_stat_division_loop
-	ret

2. Delete remaining Exp. Share item checks

The only instance left where IsAnyMonHoldingExpShare is being used is in PlayVictoryMusic, which was appropriate since the first function not only checks if at least one Pokémon has the Exp. Share equipped, but also if they're alive; if the check is passed, then we won the battle, playing the victory music. Now that the Exp. Share is a toggleable Key item it can't be used to reliably tell if we have at least one Pokémon alive. Delete its usage in PlayVictoryMusic, located in engine/battle/core.asm:

 PlayVictoryMusic:
 	push de
 	ld de, MUSIC_NONE
 	call PlayMusic
 	call DelayFrame
 	ld de, MUSIC_WILD_VICTORY
 	ld a, [wBattleMode]
 	dec a
 	jr nz, .trainer_victory
-	push de
-	call IsAnyMonHoldingExpShare
-	pop de
-	jr nz, .play_music
 	ld hl, wPayDayMoney
 	...

Finally, we can safely delete the IsAnyMonHoldingExpShare function itself located in the same file, since it's now completely unused:

-IsAnyMonHoldingExpShare:
-	ld a, [wPartyCount]
-	ld b, a
-	ld hl, wPartyMon1
-	ld c, 1
-	ld d, 0
-.loop
-	push hl
-	push bc
-	ld bc, MON_HP
-	add hl, bc
-	ld a, [hli]
-	or [hl]
-	pop bc
-	pop hl
-	jr z, .next
-
-	push hl
-	push bc
-	ld bc, MON_ITEM
-	add hl, bc
-	pop bc
-	ld a, [hl]
-	pop hl
-
-	cp EXP_SHARE
-	jr nz, .next
-	ld a, d
-	or c
-	ld d, a
-
-.next
-	sla c
-	push de
-	ld de, PARTYMON_STRUCT_LENGTH
-	add hl, de
-	pop de
-	dec b
-	jr nz, .loop
-
-	ld a, d
-	ld e, 0
-	ld b, PARTY_LENGTH
-.loop2
-	srl a
-	jr nc, .okay
-	inc e
-
-.okay
-	dec b
-	jr nz, .loop2
-	ld a, e
-	and a
-	ret

3. Delete unused labels in WRAM

With all the stuff we changed/deleted, some WRAM labels became unused and can safely be replaced with empty allocated bytes. For this, go to ram/wram.asm:

 wPlayerFutureSightCount:: db
 wEnemyFutureSightCount:: db
 
-wGivingExperienceToExpShareHolders:: db
-
-wBackupEnemyMonBaseStats:: ds NUM_EXP_STATS
-wBackupEnemyMonCatchRate:: db
-wBackupEnemyMonBaseExp:: db
+	ds 8
 wPlayerFutureSightDamage:: dw
 wEnemyFutureSightDamage:: dw

By allocating empty space in memory we know there are empty unused bytes we can replace in the future for new WRAM labels. Also we avoid accidentally shifting memory addresses, probably rendering our save file useless. (If you don't know how ds, db or dw work, please refer to the RGBDS's assembly syntax documentation).

And that's it! It's been a long tutorial, but we're finally done reworking the experience system along with the Exp. Share item to work like in generation 6. Remember to balance your romhack appropriately!

Exp. Share Key Item Exp. Share ON Exp. Share OFF

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