Skip to content

Commit

Permalink
New Feature: Level Caps (#3632)
Browse files Browse the repository at this point in the history
* New Feature: Level Caps

* B_LEVEL_CAP_EXP_UP and fixes

* 1 exp fix for hard level caps

* remove 1 exp hack

* Reviews applied

* fix u8/u16

---------

Co-authored-by: Bassoonian <iasperbassoonian@gmail.com>
  • Loading branch information
AlexOn1ine and Bassoonian authored Jan 9, 2024
1 parent 4a3954f commit c56acb9
Show file tree
Hide file tree
Showing 8 changed files with 123 additions and 7 deletions.
37 changes: 37 additions & 0 deletions include/level_caps.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#ifndef GUARD_LEVEL_CAP_H
#define GUARD_LEVEL_CAP_H

// experience (soft-)caps

#define EXP_CAP_NONE 0 // Regular behavior, no level caps are applied
#define EXP_CAP_HARD 1 // Pokémon with a level >= the level cap cannot gain any experience
#define EXP_CAP_SOFT 2 // Pokémon with a level >= the level cap will gain reduced experience

#define LEVEL_CAP_NONE 0 // No level cap, only applicable if B_EXP_CAP_TYPE is EXP_CAP_NONE
#define LEVEL_CAP_FLAG_LIST 1 // Level cap is chosen according to the first unset flag in `sLevelCapFlagMap`
#define LEVEL_CAP_VARIABLE 2 // Level cap is chosen according to the contents of the event variable specified by B_LEVEL_CAP_VARIABLE

#define B_EXP_CAP_TYPE EXP_CAP_NONE // [EXP_CAP_NONE, EXP_CAP_HARD, EXP_CAP_SOFT] choose the type of level cap to apply
#define B_LEVEL_CAP_TYPE LEVEL_CAP_NONE // [LEVEL_CAP_NONE, LEVEL_CAP_FLAG_LIST, LEVEL_CAP_VARIABLE] choose the method to derive the level cap
#define B_LEVEL_CAP_VARIABLE 0 // event variable used to derive level cap if B_LEVEL_CAP_TYPE is set to LEVEL_CAP_VARIABLE

#define B_RARE_CANDY_CAP FALSE // If set to true, Rare Candies can't be used to go over the level cap
#define B_LEVEL_CAP_EXP_UP FALSE // If set to true, mons under level cap will receive more experience

#if B_EXP_CAP_TYPE != EXP_CAP_NONE && B_EXP_CAP_TYPE != EXP_CAP_HARD && B_EXP_CAP_TYPE != EXP_CAP_SOFT
#error "Invalid choice for B_EXP_CAP_TYPE, must be of [EXP_CAP_NONE, EXP_CAP_HARD, EXP_CAP_SOFT]"
#endif

#if B_EXP_CAP_TYPE == EXP_CAP_HARD || B_EXP_CAP_TYPE == EXP_CAP_SOFT
#if B_LEVEL_CAP_TYPE != LEVEL_CAP_FLAG_LIST && B_LEVEL_CAP_TYPE != LEVEL_CAP_VARIABLE
#error "Invalid choice for B_LEVEL_CAP_TYPE, must be of [LEVEL_CAP_FLAG_LIST, LEVEL_CAP_VARIABLE]"
#endif
#if B_LEVEL_CAP_TYPE == LEVEL_CAP_VARIABLE && B_LEVEL_CAP_VARIABLE == 0
#error "B_LEVEL_CAP_TYPE set to LEVEL_CAP_VARIABLE, but no variable chosen for B_LEVEL_CAP_VARIABLE, set B_LEVEL_CAP_VARIABLE to a valid event variable"
#endif
#endif

u32 GetCurrentLevelCap(void);
u32 GetSoftLevelCapExpValue(u32 level, u32 expValue);

#endif /* GUARD_LEVEL_CAP_H */
3 changes: 2 additions & 1 deletion src/battle_controller_player.c
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
#include "constants/songs.h"
#include "constants/trainers.h"
#include "constants/rgb.h"
#include "level_caps.h"

static void PlayerBufferExecCompleted(u32 battler);
static void PlayerHandleLoadMonSprite(u32 battler);
Expand Down Expand Up @@ -2145,7 +2146,7 @@ void PlayerHandleExpUpdate(u32 battler)
u8 monId = gBattleResources->bufferA[battler][1];
s32 taskId, expPointsToGive;

if (GetMonData(&gPlayerParty[monId], MON_DATA_LEVEL) >= MAX_LEVEL)
if (GetMonData(&gPlayerParty[monId], MON_DATA_LEVEL) >= GetCurrentLevelCap())
{
PlayerBufferExecCompleted(battler);
}
Expand Down
3 changes: 2 additions & 1 deletion src/battle_interface.c
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
#include "constants/rgb.h"
#include "constants/songs.h"
#include "constants/items.h"
#include "level_caps.h"

enum
{ // Corresponds to gHealthboxElementsGfxTable (and the tables after it) in graphics.c
Expand Down Expand Up @@ -2687,7 +2688,7 @@ static void MoveBattleBarGraphically(u8 battlerId, u8 whichBar)
&gBattleSpritesDataPtr->battleBars[battlerId].currValue,
array, B_EXPBAR_PIXELS / 8);
level = GetMonData(&gPlayerParty[gBattlerPartyIndexes[battlerId]], MON_DATA_LEVEL);
if (level == MAX_LEVEL)
if (level >= GetCurrentLevelCap())
{
for (i = 0; i < 8; i++)
array[i] = 0;
Expand Down
5 changes: 3 additions & 2 deletions src/battle_script_commands.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
#include "bg.h"
#include "string_util.h"
#include "pokemon_icon.h"
#include "level_caps.h"
#include "m4a.h"
#include "mail.h"
#include "event_data.h"
Expand Down Expand Up @@ -4239,14 +4240,14 @@ static void Cmd_getexp(void)
if (IsValidForBattle(&gPlayerParty[*expMonId]))
{
if (wasSentOut)
gBattleMoveDamage = gBattleStruct->expValue;
gBattleMoveDamage = GetSoftLevelCapExpValue(gPlayerParty[*expMonId].level, gBattleStruct->expValue);
else
gBattleMoveDamage = 0;

if ((holdEffect == HOLD_EFFECT_EXP_SHARE || IsGen6ExpShareEnabled())
&& (B_SPLIT_EXP < GEN_6 || gBattleMoveDamage == 0)) // only give exp share bonus in later gens if the mon wasn't sent out
{
gBattleMoveDamage += gBattleStruct->expShareExpValue;
gBattleMoveDamage += GetSoftLevelCapExpValue(gPlayerParty[*expMonId].level, gBattleStruct->expShareExpValue);;
}

ApplyExperienceMultipliers(&gBattleMoveDamage, *expMonId, gBattlerFainted);
Expand Down
5 changes: 4 additions & 1 deletion src/daycare.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include "battle.h"
#include "daycare.h"
#include "string_util.h"
#include "level_caps.h"
#include "mail.h"
#include "pokemon_storage_system.h"
#include "event_data.h"
Expand Down Expand Up @@ -316,7 +317,7 @@ static u16 TakeSelectedPokemonFromDaycare(struct DaycareMon *daycareMon)
species = newSpecies;
}

if (GetMonData(&pokemon, MON_DATA_LEVEL) != MAX_LEVEL)
if (GetMonData(&pokemon, MON_DATA_LEVEL) < GetCurrentLevelCap())
{
experience = GetMonData(&pokemon, MON_DATA_EXP) + daycareMon->steps;
SetMonData(&pokemon, MON_DATA_EXP, &experience);
Expand Down Expand Up @@ -365,6 +366,8 @@ static u8 GetNumLevelsGainedFromSteps(struct DaycareMon *daycareMon)

levelBefore = GetLevelFromBoxMonExp(&daycareMon->mon);
levelAfter = GetLevelAfterDaycareSteps(&daycareMon->mon, daycareMon->steps);
if (levelAfter > GetCurrentLevelCap())
levelAfter = GetCurrentLevelCap();
return levelAfter - levelBefore;
}

Expand Down
71 changes: 71 additions & 0 deletions src/level_caps.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
#include "global.h"
#include "battle.h"
#include "event_data.h"
#include "level_caps.h"
#include "pokemon.h"


u32 GetCurrentLevelCap(void)
{
static const u32 sLevelCapFlagMap[][2] =
{
{FLAG_BADGE01_GET, 15},
{FLAG_BADGE02_GET, 19},
{FLAG_BADGE03_GET, 24},
{FLAG_BADGE04_GET, 29},
{FLAG_BADGE05_GET, 31},
{FLAG_BADGE06_GET, 33},
{FLAG_BADGE07_GET, 42},
{FLAG_BADGE08_GET, 46},
{FLAG_IS_CHAMPION, 58},
};

u32 i;

if (B_LEVEL_CAP_TYPE == LEVEL_CAP_FLAG_LIST)
{
for (i = 0; i < ARRAY_COUNT(sLevelCapFlagMap); i++)
{
if (!FlagGet(sLevelCapFlagMap[i][0]))
return sLevelCapFlagMap[i][1];
}
}
else if (B_LEVEL_CAP_TYPE == LEVEL_CAP_VARIABLE)
{
return VarGet(B_LEVEL_CAP_VARIABLE);
}

return MAX_LEVEL;
}

u32 GetSoftLevelCapExpValue(u32 level, u32 expValue)
{
static const u32 sExpScalingDown[5] = { 4, 8, 16, 32, 64 };
static const u32 sExpScalingUp[5] = { 16, 8, 4, 2, 1 };

u32 levelDifference;
u32 currentLevelCap = GetCurrentLevelCap();

if (B_EXP_CAP_TYPE == EXP_CAP_NONE)
return expValue;

if (B_LEVEL_CAP_EXP_UP && level < currentLevelCap)
{
levelDifference = currentLevelCap - level;
if (levelDifference > ARRAY_COUNT(sExpScalingDown))
return expValue + (expValue / sExpScalingUp[ARRAY_COUNT(sExpScalingDown) - 1]);
else
return expValue + (expValue / sExpScalingUp[levelDifference]);
}
else if (B_EXP_CAP_TYPE == EXP_CAP_SOFT && level >= currentLevelCap)
{
levelDifference = level - currentLevelCap;
if (levelDifference > ARRAY_COUNT(sExpScalingDown))
return expValue / sExpScalingDown[ARRAY_COUNT(sExpScalingDown) - 1];
else
return expValue / sExpScalingDown[levelDifference];
}
else
return 0;

}
3 changes: 2 additions & 1 deletion src/party_menu.c
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#include "item.h"
#include "item_menu.h"
#include "item_use.h"
#include "level_caps.h"
#include "link.h"
#include "link_rfu.h"
#include "mail.h"
Expand Down Expand Up @@ -5512,7 +5513,7 @@ void ItemUseCB_RareCandy(u8 taskId, TaskFunc task)
u8 holdEffectParam = ItemId_GetHoldEffectParam(*itemPtr);

sInitialLevel = GetMonData(mon, MON_DATA_LEVEL);
if (sInitialLevel != MAX_LEVEL)
if (!(B_RARE_CANDY_CAP && sInitialLevel >= GetCurrentLevelCap()))
{
BufferMonStatsToTaskData(mon, arrayPtr);
cannotUseEffect = ExecuteTableBasedItemEffect(mon, *itemPtr, gPartyMenu.slotId, 0);
Expand Down
3 changes: 2 additions & 1 deletion src/pokemon.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include "field_weather.h"
#include "graphics.h"
#include "item.h"
#include "level_caps.h"
#include "link.h"
#include "main.h"
#include "overworld.h"
Expand Down Expand Up @@ -5054,7 +5055,7 @@ bool8 TryIncrementMonLevel(struct Pokemon *mon)
expPoints = gExperienceTables[gSpeciesInfo[species].growthRate][MAX_LEVEL];
SetMonData(mon, MON_DATA_EXP, &expPoints);
}
if (nextLevel > MAX_LEVEL || expPoints < gExperienceTables[gSpeciesInfo[species].growthRate][nextLevel])
if (nextLevel > GetCurrentLevelCap() || expPoints < gExperienceTables[gSpeciesInfo[species].growthRate][nextLevel])
{
return FALSE;
}
Expand Down

0 comments on commit c56acb9

Please sign in to comment.