-
Notifications
You must be signed in to change notification settings - Fork 1k
Experience System & Exp. All Enhancements (Single message, etc)
This is a transcription of a tutorial suloku posted on PasteBin. View that for a more thorough explanation of the Exp. All's issues, including examples.
In RBY, the Exp. All is kind of...silly. Here's how it works.
- Total experience is divided by two.
- 1/2 goes to the pokémon that participated in the battle.
- 1/2 is to be shared among the whole party, which is then divided amongst them.
- Traded EXP boost is applied (so any traded Pokemon gets an additional 1.5 bonus)
Due to the way this works, when shared amongst a party of 6, Pokemon actually end up getting 1/12 of the exp gained. This makes the Exp. All a largely useless item outside of exploiting division errors to get 0 ATK Stat Exp on a Pokemon (optimising for confusion damage), or getting maximum Stat Exp on a low-level Pokemon, both of which take an extremely long time to perform anyway. With a use case this narrow, it's no wonder you're here reading this tutorial!
Implementing these fixes does have some minor side effects, which will be covered as we go.
First, we should look at the exp division. We will fix this with a very simple change in engine/battle/experience.asm
, by removing a call to DivideExpDataByNumMonsGainingExp
, which is what divides the experience amongst the party members.
GainExperience:
ld a, [wLinkState]
cp LINK_STATE_BATTLING
ret z ; return if link battle
- call DivideExpDataByNumMonsGainingExp ;--> we are deleting this line!
ld hl, wPartyMon1
xor a
ld [wWhichPokemon], a
And because that function now goes unused, you can delete it. It should be around line 303:
; divide enemy base stats, catch rate, and base exp by the number of mons gaining exp
-DivideExpDataByNumMonsGainingExp:
- ld a, [wPartyGainExpFlags]
- ld b, a
- xor a
- ld c, $8
- ld d, $0
-.countSetBitsLoop ; loop to count set bits in wPartyGainExpFlags
- xor a
- srl b
- adc d
- ld d, a
- dec c
- jr nz, .countSetBitsLoop
- cp $2
- ret c ; return if only one mon is gaining exp
- ld [wd11e], a ; store number of mons gaining exp
- ld hl, wEnemyMonBaseStats
- ld c, wEnemyMonBaseExp + 1 - wEnemyMonBaseStats
-.divideLoop
- xor a
- ldh [hDividend], a
- ld a, [hl]
- ldh [hDividend + 1], a
- ld a, [wd11e]
- ldh [hDivisor], a
- ld b, $2
- call Divide ; divide value by number of mons gaining exp
- ldh a, [hQuotient + 3]
- ld [hli], a
- dec c
-- jr nz, .divideLoop
- ret
This results in a side effect, though: All participants will get 100% EXP when not using the EXP. All, which is actually better than XY's Experience System.
Implementing just this results in two issues:
-
Using the Exp. All, you will get an Exp message for every participant in the battle, plus 1 message per team member. This is extremely slow and makes using it tiresome, so two actions should be done: 1.1) Maintain the messages for participating pokémon 1.2) Show only one generic message for the whole party
-
1 Experience Point is lost on odd exp yields since halved EXP is rounded down, so if a Pokémon yields 81 exp, participants will get 40 + 40 Exp foro a total of 80 exp. This is a minor inconvenience, but can be solved by rounding the halved EXP up instead of down in
engine\battle\core.asm
.
Both of these are perfectly solvable, so let's patch them up.
Anyone who's used the Exp. All has experienced the agony as it tells you how strong your entire party has become. Let's remove that! If you're not interested in using the modern experience system, you can walk away with just this implemented.
Let's start from the top of GainExperience
. Implement as follows;
GainExperience:
ld a, [wLinkState]
cp LINK_STATE_BATTLING
ret z ; return if link battle
call DivideExpDataByNumMonsGainingExp ; if you're using the gen 6 exp system, remove this too
+ ld a, [wBoostExpByExpAll] ;load in a if the EXP All is being used
+ ld hl, WithExpAllText ; this is preparing the text to show
+ and a ;check wBoostExpByExpAll value
+ jr z, .skipExpAll ; if wBoostExpByExpAll is zero, we are not using it, so we don't show anything and keep going on
+ call PrintText ; if the code reaches this point it means we have the Exp.All, so show the message
+.skipExpAll
ld hl, wPartyMon1
xor a
- ld [wWhichPokemon], a
- ld hl, wPartyMon1
- xor a
ld [wWhichPokemon], a
.partyMonLoop ; loop over each mon and add gained exp
This sets up the text we want to use later. You'll notice the "with Exp. All" text is being repurposed, we'll get to that in a second!
Now go to GainExperience.next2
. Ctrl+F ".next2" gets you there.
.next2
push hl
ld a, [wWhichPokemon]
ld hl, wPartyMonNicks
call GetPartyMonName
- ld hl, GainedText
+ ld a, [wBoostExpByExpAll] ; get using ExpAll flag
+ and a ; check the flag
+ jr nz, .skipExpText ; if there's EXP. all, skip showing any text
+ ld hl, GainedText ;there's no EXP. all, load the text to show
call PrintText
+.skipExpText
xor a ; PLAYER_PARTY_DATA
ld [wMonDataLocation], a
This checks if the Exp. All is active, and sets up the usual "gained xxxx Exp!" message in the usual way.
We're going to repurpose some text here. Go to data\text\text_2.asm
and make the following changes;
_WithExpAllText::
text "with EXP.ALL,"
cont "@"
text_end
As-is, this will make your text look a bit weird, so let's repurpose it to look like this:
_WithExpAllText::
+ text "Party gained"
+ next "@"
text_end
Now your Exp gains will look like this: "PIKACHU gained" "xxxx EXP. Points!" "Party gained" "xxxx EXP. Points!"
However, you will notice a couple of seconds of lag after that last message has been put out. This is pretty much unavoidable: What's happening there, is the game is calculating the stat gains from the experience given. It does still end up being faster than the original Exp. All, but it's still quite jarring.