From e146940f250fc5dda02076e5cb8d09b62fd0156b Mon Sep 17 00:00:00 2001 From: psf <77138753+pkmnsnfrn@users.noreply.github.com> Date: Sat, 12 Apr 2025 01:23:48 -0700 Subject: [PATCH] Add B_LEVEL_UP_NOTIFICATION to improve player QoL when performing multiple level ups (#4901) Co-authored-by: Alex <93446519+AlexOn1ine@users.noreply.github.com> --- include/battle.h | 1 + include/config/battle.h | 1 + include/pokemon.h | 1 + src/battle_controller_player.c | 21 ++++++++++++++------- src/battle_script_commands.c | 33 +++++++++++++++++++++++++++++---- src/pokemon.c | 8 ++++++-- 6 files changed, 52 insertions(+), 13 deletions(-) diff --git a/include/battle.h b/include/battle.h index dbeb791867..9a627b16b0 100644 --- a/include/battle.h +++ b/include/battle.h @@ -396,6 +396,7 @@ struct BattleCallbacksStack struct StatsArray { u16 stats[NUM_STATS]; + u16 level; }; struct BattleResources diff --git a/include/config/battle.h b/include/config/battle.h index 59c90aacb0..a252edba92 100644 --- a/include/config/battle.h +++ b/include/config/battle.h @@ -16,6 +16,7 @@ #define B_SPLIT_EXP GEN_LATEST // In Gen6+, all participating mon get full experience. #define B_SCALED_EXP GEN_LATEST // In Gen5 and Gen7+, experience is weighted by level difference. #define B_UNEVOLVED_EXP_MULTIPLIER GEN_LATEST // In Gen6+, if the Pokémon is at or past the level where it would be able to evolve, but it has not, it gets a ~1.2 multiplier to EXP gain. Only applies to Pokémon with EVO_LEVEL method. +#define B_LEVEL_UP_NOTIFICATION GEN_LATEST // In Gen9+, if the Pokémon gets enough experience to level up multiple times, the message is only displayed once. // Stat settings #define B_BADGE_BOOST GEN_LATEST // In Gen4+, Gym Badges no longer boost a Pokémon's stats. diff --git a/include/pokemon.h b/include/pokemon.h index 0c3deb2f72..7e048ef015 100644 --- a/include/pokemon.h +++ b/include/pokemon.h @@ -658,6 +658,7 @@ void SetMonMoveSlot(struct Pokemon *mon, u16 move, u8 slot); void SetBattleMonMoveSlot(struct BattlePokemon *mon, u16 move, u8 slot); void GiveMonInitialMoveset(struct Pokemon *mon); void GiveBoxMonInitialMoveset(struct BoxPokemon *boxMon); +u16 MonTryLearningNewMoveAtLevel(struct Pokemon *mon, bool32 firstMove, u32 level); u16 MonTryLearningNewMove(struct Pokemon *mon, bool8 firstMove); void DeleteFirstMoveAndGiveMoveToMon(struct Pokemon *mon, u16 move); void DeleteFirstMoveAndGiveMoveToBoxMon(struct BoxPokemon *boxMon, u16 move); diff --git a/src/battle_controller_player.c b/src/battle_controller_player.c index dbbfeb40db..cd69f2ab1a 100644 --- a/src/battle_controller_player.c +++ b/src/battle_controller_player.c @@ -1453,11 +1453,13 @@ static void Task_GiveExpToMon(u8 taskId) u8 level = GetMonData(mon, MON_DATA_LEVEL); u32 currExp = GetMonData(mon, MON_DATA_EXP); u32 nextLvlExp = gExperienceTables[gSpeciesInfo[species].growthRate][level + 1]; + u32 expAfterGain = currExp + gainedExp; u32 oldMaxHP = GetMonData(mon, MON_DATA_MAX_HP); - if (currExp + gainedExp >= nextLvlExp) + if (expAfterGain >= nextLvlExp) { - SetMonData(mon, MON_DATA_EXP, &nextLvlExp); + SetMonData(mon, MON_DATA_EXP, (B_LEVEL_UP_NOTIFICATION >= GEN_9) ? &expAfterGain : &nextLvlExp); + CalculateMonStats(mon); // Reapply Dynamax HP multiplier after stats are recalculated. @@ -1465,7 +1467,7 @@ static void Task_GiveExpToMon(u8 taskId) DynamaxModifyHPLevelUp(mon, battler, oldMaxHP); gainedExp -= nextLvlExp - currExp; - BtlController_EmitTwoReturnValues(battler, BUFFER_B, RET_VALUE_LEVELED_UP, gainedExp); + BtlController_EmitTwoReturnValues(battler, BUFFER_B, RET_VALUE_LEVELED_UP, (B_LEVEL_UP_NOTIFICATION >= GEN_9) ? 0 : gainedExp); if (IsDoubleBattle() == TRUE && (monId == gBattlerPartyIndexes[battler] || monId == gBattlerPartyIndexes[BATTLE_PARTNER(battler)])) @@ -1509,7 +1511,7 @@ static void Task_PrepareToGiveExpWithExpBar(u8 taskId) static void Task_GiveExpWithExpBar(u8 taskId) { - u8 level; + u32 level, expAfterGain; u16 species; u32 oldMaxHP; s32 currExp, expOnNextLvl, newExpPoints; @@ -1536,9 +1538,14 @@ static void Task_GiveExpWithExpBar(u8 taskId) oldMaxHP = GetMonData(mon, MON_DATA_MAX_HP); expOnNextLvl = gExperienceTables[gSpeciesInfo[species].growthRate][level + 1]; - if (currExp + gainedExp >= expOnNextLvl) + expAfterGain = currExp + gainedExp; + if (expAfterGain >= expOnNextLvl) { - SetMonData(mon, MON_DATA_EXP, &expOnNextLvl); + if (B_LEVEL_UP_NOTIFICATION >= GEN_9) + SetMonData(mon, MON_DATA_EXP, &expAfterGain); + else + SetMonData(mon, MON_DATA_EXP, &expOnNextLvl); + CalculateMonStats(mon); // Reapply Dynamax HP multiplier after stats are recalculated. @@ -1546,7 +1553,7 @@ static void Task_GiveExpWithExpBar(u8 taskId) DynamaxModifyHPLevelUp(mon, battler, oldMaxHP); gainedExp -= expOnNextLvl - currExp; - BtlController_EmitTwoReturnValues(battler, BUFFER_B, RET_VALUE_LEVELED_UP, gainedExp); + BtlController_EmitTwoReturnValues(battler, BUFFER_B, RET_VALUE_LEVELED_UP, (B_LEVEL_UP_NOTIFICATION >= GEN_9) ? 0 : gainedExp); gTasks[taskId].func = Task_LaunchLvlUpAnim; } else diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index dca87252e0..671203448b 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -5100,6 +5100,7 @@ static void Cmd_getexp(void) u32 holdEffect; s32 i; // also used as stringId u8 *expMonId = &gBattleStruct->expGetterMonId; + u32 currLvl; gBattlerFainted = GetBattlerForBattleScript(cmd->battler); @@ -5314,7 +5315,8 @@ static void Cmd_getexp(void) if (gBattleControllerExecFlags == 0) { gBattleResources->bufferB[gBattleStruct->expGetterBattlerId][0] = 0; - if (GetMonData(&gPlayerParty[*expMonId], MON_DATA_HP) && GetMonData(&gPlayerParty[*expMonId], MON_DATA_LEVEL) != MAX_LEVEL) + currLvl = GetMonData(&gPlayerParty[*expMonId], MON_DATA_LEVEL); + if (GetMonData(&gPlayerParty[*expMonId], MON_DATA_HP) && currLvl != MAX_LEVEL) { gBattleResources->beforeLvlUp->stats[STAT_HP] = GetMonData(&gPlayerParty[*expMonId], MON_DATA_MAX_HP); gBattleResources->beforeLvlUp->stats[STAT_ATK] = GetMonData(&gPlayerParty[*expMonId], MON_DATA_ATK); @@ -5322,6 +5324,7 @@ static void Cmd_getexp(void) gBattleResources->beforeLvlUp->stats[STAT_SPEED] = GetMonData(&gPlayerParty[*expMonId], MON_DATA_SPEED); gBattleResources->beforeLvlUp->stats[STAT_SPATK] = GetMonData(&gPlayerParty[*expMonId], MON_DATA_SPATK); gBattleResources->beforeLvlUp->stats[STAT_SPDEF] = GetMonData(&gPlayerParty[*expMonId], MON_DATA_SPDEF); + gBattleResources->beforeLvlUp->level = currLvl; BtlController_EmitExpUpdate(gBattleStruct->expGetterBattlerId, BUFFER_A, *expMonId, gBattleStruct->battlerExpReward); MarkBattlerForControllerExec(gBattleStruct->expGetterBattlerId); @@ -8643,10 +8646,32 @@ static void Cmd_handlelearnnewmove(void) { CMD_ARGS(const u8 *learnedMovePtr, const u8 *nothingToLearnPtr, bool8 isFirstMove); + u16 learnMove = MOVE_NONE; u32 monId = gBattleStruct->expGetterMonId; - u16 learnMove = MonTryLearningNewMove(&gPlayerParty[monId], cmd->isFirstMove); - while (learnMove == MON_ALREADY_KNOWS_MOVE) - learnMove = MonTryLearningNewMove(&gPlayerParty[monId], FALSE); + + if (B_LEVEL_UP_NOTIFICATION >= GEN_9) + { + u32 currLvl = GetMonData(&gPlayerParty[monId], MON_DATA_LEVEL); + + while (gBattleResources->beforeLvlUp->level <= currLvl) + { + learnMove = MonTryLearningNewMoveAtLevel(&gPlayerParty[monId], cmd->isFirstMove, gBattleResources->beforeLvlUp->level); + + while (learnMove == MON_ALREADY_KNOWS_MOVE) + learnMove = MonTryLearningNewMoveAtLevel(&gPlayerParty[monId], FALSE, gBattleResources->beforeLvlUp->level); + + if (learnMove != MOVE_NONE) + break; + + gBattleResources->beforeLvlUp->level++; + } + } + else + { + learnMove = MonTryLearningNewMove(&gPlayerParty[monId], cmd->isFirstMove); + while (learnMove == MON_ALREADY_KNOWS_MOVE) + learnMove = MonTryLearningNewMove(&gPlayerParty[monId], FALSE); + } if (learnMove == MOVE_NONE || RECORDED_WILD_BATTLE) { diff --git a/src/pokemon.c b/src/pokemon.c index 0289ca180e..29407b8961 100644 --- a/src/pokemon.c +++ b/src/pokemon.c @@ -1982,11 +1982,10 @@ void GiveBoxMonInitialMoveset(struct BoxPokemon *boxMon) //Credit: AsparagusEdua } } -u16 MonTryLearningNewMove(struct Pokemon *mon, bool8 firstMove) +u16 MonTryLearningNewMoveAtLevel(struct Pokemon *mon, bool32 firstMove, u32 level) { u32 retVal = MOVE_NONE; u16 species = GetMonData(mon, MON_DATA_SPECIES, NULL); - u8 level = GetMonData(mon, MON_DATA_LEVEL, NULL); const struct LevelUpMove *learnset = GetSpeciesLevelUpLearnset(species); // since you can learn more than one move per level @@ -2034,6 +2033,11 @@ u16 MonTryLearningNewMove(struct Pokemon *mon, bool8 firstMove) return retVal; } +u16 MonTryLearningNewMove(struct Pokemon *mon, bool8 firstMove) +{ + return MonTryLearningNewMoveAtLevel(mon, firstMove, GetMonData(mon, MON_DATA_LEVEL, NULL)); +} + void DeleteFirstMoveAndGiveMoveToMon(struct Pokemon *mon, u16 move) { s32 i;