Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New Feature: Level Caps #3632

Merged
merged 9 commits into from
Jan 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading