Generation 6 Experience Share (#3276)

This commit is contained in:
psf 2023-09-07 19:18:18 -07:00 committed by GitHub
parent 820113d883
commit 75d06bb599
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 120 additions and 35 deletions

View File

@ -219,6 +219,7 @@ void CopyMonLevelAndBaseStatsToBattleMon(u32 battler, struct Pokemon *mon);
void CopyMonAbilityAndTypesToBattleMon(u32 battler, struct Pokemon *mon); void CopyMonAbilityAndTypesToBattleMon(u32 battler, struct Pokemon *mon);
void RecalcBattlerStats(u32 battler, struct Pokemon *mon); void RecalcBattlerStats(u32 battler, struct Pokemon *mon);
bool32 IsAlly(u32 battlerAtk, u32 battlerDef); bool32 IsAlly(u32 battlerAtk, u32 battlerDef);
bool32 IsGen6ExpShareEnabled(void);
// Ability checks // Ability checks
bool32 IsRolePlayBannedAbilityAtk(u16 ability); bool32 IsRolePlayBannedAbilityAtk(u16 ability);

View File

@ -16,6 +16,10 @@
// TM config // TM config
#define I_REUSABLE_TMS FALSE // In Gen5-8, TMs are reusable. Setting this to TRUE will make all vanilla TMs reusable, though they can also be cherry-picked by setting their importance to 1. #define I_REUSABLE_TMS FALSE // In Gen5-8, TMs are reusable. Setting this to TRUE will make all vanilla TMs reusable, though they can also be cherry-picked by setting their importance to 1.
// Exp. Share config
#define I_EXP_SHARE_FLAG 0 // If this flag is set, every Pokémon in the party will gain experience, regardless if they participated in the battle or not.
#define I_EXP_SHARE_ITEM GEN_5 // In Gen6+, the Exp. Share was changed from a held item to a Key item that toggles the effect described above.
// Repel/Lure config // Repel/Lure config
// These two settings are both independent and complementary. // These two settings are both independent and complementary.
#define VAR_LAST_REPEL_LURE_USED 0 // If this var has been assigned, last Repel/Lure used will be saved and the player will get prompted with the vanilla repel YES/NO option, unless I_REPEL_LURE_MENU is set to TRUE. #define VAR_LAST_REPEL_LURE_USED 0 // If this var has been assigned, last Repel/Lure used will be saved and the player will get prompted with the vanilla repel YES/NO option, unless I_REPEL_LURE_MENU is set to TRUE.

View File

@ -667,8 +667,9 @@
#define STRINGID_PKMNITEMMELTED 665 #define STRINGID_PKMNITEMMELTED 665
#define STRINGID_ULTRABURSTREACTING 666 #define STRINGID_ULTRABURSTREACTING 666
#define STRINGID_ULTRABURSTCOMPLETED 667 #define STRINGID_ULTRABURSTCOMPLETED 667
#define STRINGID_TEAMGAINEDEXP 668
#define BATTLESTRINGS_COUNT 668 #define BATTLESTRINGS_COUNT 669
// This is the string id that gBattleStringsTable starts with. // This is the string id that gBattleStringsTable starts with.
// String ids before this (e.g. STRINGID_INTROMSG) are not in the table, // String ids before this (e.g. STRINGID_INTROMSG) are not in the table,

View File

@ -139,6 +139,8 @@
// It looks like file.c:line: size of array `id' is negative // It looks like file.c:line: size of array `id' is negative
#define STATIC_ASSERT(expr, id) typedef char id[(expr) ? 1 : -1]; #define STATIC_ASSERT(expr, id) typedef char id[(expr) ? 1 : -1];
#define FEATURE_FLAG_ASSERT(flag, id) STATIC_ASSERT(flag > TEMP_FLAGS_END || flag == 0, id)
struct Coords8 struct Coords8
{ {
s8 x; s8 x;

View File

@ -30,6 +30,7 @@ void ItemUseOutOfBattle_FormChange(u8);
void ItemUseOutOfBattle_FormChange_ConsumedOnUse(u8); void ItemUseOutOfBattle_FormChange_ConsumedOnUse(u8);
void ItemUseOutOfBattle_Honey(u8); void ItemUseOutOfBattle_Honey(u8);
void ItemUseOutOfBattle_CannotUse(u8); void ItemUseOutOfBattle_CannotUse(u8);
void ItemUseOutOfBattle_ExpShare(u8);
void ItemUseInBattle_BagMenu(u8 taskId); void ItemUseInBattle_BagMenu(u8 taskId);
void ItemUseInBattle_PartyMenu(u8 taskId); void ItemUseInBattle_PartyMenu(u8 taskId);
void ItemUseInBattle_PartyMenuChooseMove(u8 taskId); void ItemUseInBattle_PartyMenuChooseMove(u8 taskId);

View File

@ -3034,4 +3034,8 @@ extern const u8 gText_BoxName[];
extern const u8 gText_PkmnsNickname[]; extern const u8 gText_PkmnsNickname[];
extern const u8 gText_TellHimTheWords[]; extern const u8 gText_TellHimTheWords[];
// Exp. Share
extern const u8 gText_ExpShareOn[];
extern const u8 gText_ExpShareOff[];
#endif // GUARD_STRINGS_H #endif // GUARD_STRINGS_H

View File

@ -803,7 +803,7 @@ static const u8 sText_MirrorHerbCopied[] = _("{B_SCR_ACTIVE_NAME_WITH_PREFIX} us
static const u8 sText_PkmnItemMelted[] = _("{B_ATK_NAME_WITH_PREFIX} corroded\n{B_DEF_NAME_WITH_PREFIX}'s {B_LAST_ITEM}!"); static const u8 sText_PkmnItemMelted[] = _("{B_ATK_NAME_WITH_PREFIX} corroded\n{B_DEF_NAME_WITH_PREFIX}'s {B_LAST_ITEM}!");
static const u8 sText_UltraBurstReacting[] = _("Bright light is about to\nburst out of {B_ATK_NAME_WITH_PREFIX}!"); static const u8 sText_UltraBurstReacting[] = _("Bright light is about to\nburst out of {B_ATK_NAME_WITH_PREFIX}!");
static const u8 sText_UltraBurstCompleted[] = _("{B_ATK_NAME_WITH_PREFIX} regained its\ntrue power through Ultra Burst!"); static const u8 sText_UltraBurstCompleted[] = _("{B_ATK_NAME_WITH_PREFIX} regained its\ntrue power through Ultra Burst!");
static const u8 sText_TeamGainedEXP[] = _("The rest of your team gained EXP.\nPoints thanks to the {B_LAST_ITEM}!\p");
const u8 *const gBattleStringsTable[BATTLESTRINGS_COUNT] = const u8 *const gBattleStringsTable[BATTLESTRINGS_COUNT] =
{ {
@ -1462,6 +1462,7 @@ const u8 *const gBattleStringsTable[BATTLESTRINGS_COUNT] =
[STRINGID_PKMNFROSTBITEHEALEDBY - BATTLESTRINGS_TABLE_START] = sText_PkmnFrostbiteHealedBy, [STRINGID_PKMNFROSTBITEHEALEDBY - BATTLESTRINGS_TABLE_START] = sText_PkmnFrostbiteHealedBy,
[STRINGID_ULTRABURSTREACTING - BATTLESTRINGS_TABLE_START] = sText_UltraBurstReacting, [STRINGID_ULTRABURSTREACTING - BATTLESTRINGS_TABLE_START] = sText_UltraBurstReacting,
[STRINGID_ULTRABURSTCOMPLETED - BATTLESTRINGS_TABLE_START] = sText_UltraBurstCompleted, [STRINGID_ULTRABURSTCOMPLETED - BATTLESTRINGS_TABLE_START] = sText_UltraBurstCompleted,
[STRINGID_TEAMGAINEDEXP - BATTLESTRINGS_TABLE_START] = sText_TeamGainedEXP,
}; };
const u16 gTrainerUsedItemStringIds[] = const u16 gTrainerUsedItemStringIds[] =

View File

@ -354,6 +354,7 @@ static void BestowItem(u32 battlerAtk, u32 battlerDef);
static bool8 IsFinalStrikeEffect(u16 move); static bool8 IsFinalStrikeEffect(u16 move);
static void TryUpdateRoundTurnOrder(void); static void TryUpdateRoundTurnOrder(void);
static bool32 ChangeOrderTargetAfterAttacker(void); static bool32 ChangeOrderTargetAfterAttacker(void);
void ApplyExperienceMultipliers(s32 *expAmount, u8 expGetterMonId, u8 faintedBattler);
static void Cmd_attackcanceler(void); static void Cmd_attackcanceler(void);
static void Cmd_accuracycheck(void); static void Cmd_accuracycheck(void);
@ -4033,6 +4034,8 @@ static void Cmd_jumpbasedontype(void)
} }
} }
FEATURE_FLAG_ASSERT(I_EXP_SHARE_FLAG, YouNeedToSetTheExpShareFlagToAnUnusedFlag);
static void Cmd_getexp(void) static void Cmd_getexp(void)
{ {
CMD_ARGS(u8 battler); CMD_ARGS(u8 battler);
@ -4088,7 +4091,7 @@ static void Cmd_getexp(void)
else else
holdEffect = ItemId_GetHoldEffect(item); holdEffect = ItemId_GetHoldEffect(item);
if (holdEffect == HOLD_EFFECT_EXP_SHARE) if (holdEffect == HOLD_EFFECT_EXP_SHARE || IsGen6ExpShareEnabled())
viaExpShare++; viaExpShare++;
} }
#if (B_SCALED_EXP >= GEN_5) && (B_SCALED_EXP != GEN_6) #if (B_SCALED_EXP >= GEN_5) && (B_SCALED_EXP != GEN_6)
@ -4137,7 +4140,8 @@ static void Cmd_getexp(void)
else else
holdEffect = ItemId_GetHoldEffect(item); holdEffect = ItemId_GetHoldEffect(item);
if (holdEffect != HOLD_EFFECT_EXP_SHARE && !(gBattleStruct->sentInPokes & 1)) if ((holdEffect != HOLD_EFFECT_EXP_SHARE && !(gBattleStruct->sentInPokes & 1) && !IsGen6ExpShareEnabled())
|| GetMonData(&gPlayerParty[gBattleStruct->expGetterMonId], MON_DATA_SPECIES_OR_EGG) == SPECIES_EGG)
{ {
*(&gBattleStruct->sentInPokes) >>= 1; *(&gBattleStruct->sentInPokes) >>= 1;
gBattleScripting.getexpState = 5; gBattleScripting.getexpState = 5;
@ -4174,34 +4178,15 @@ static void Cmd_getexp(void)
else else
gBattleMoveDamage = 0; gBattleMoveDamage = 0;
// only give exp share bonus in later gens if the mon wasn't sent out if ((holdEffect == HOLD_EFFECT_EXP_SHARE || IsGen6ExpShareEnabled())
#if B_SPLIT_EXP < GEN_6 #if B_SPLIT_EXP >= GEN_6
if (holdEffect == HOLD_EFFECT_EXP_SHARE) // only give exp share bonus in later gens if the mon wasn't sent out
&& gBattleMoveDamage == 0
#endif
)
gBattleMoveDamage += gExpShareExp; gBattleMoveDamage += gExpShareExp;
#else
if (holdEffect == HOLD_EFFECT_EXP_SHARE && gBattleMoveDamage == 0) ApplyExperienceMultipliers(&gBattleMoveDamage, gBattleStruct->expGetterMonId, gBattlerFainted);
gBattleMoveDamage += gExpShareExp;
#endif
if (holdEffect == HOLD_EFFECT_LUCKY_EGG)
gBattleMoveDamage = (gBattleMoveDamage * 150) / 100;
#if B_TRAINER_EXP_MULTIPLIER <= GEN_7
if (gBattleTypeFlags & BATTLE_TYPE_TRAINER)
gBattleMoveDamage = (gBattleMoveDamage * 150) / 100;
#endif
#if (B_SCALED_EXP >= GEN_5) && (B_SCALED_EXP != GEN_6)
{
// Note: There is an edge case where if a pokemon receives a large amount of exp, it wouldn't be properly calculated
// because of multiplying by scaling factor(the value would simply be larger than an u32 can hold). Hence u64 is needed.
u64 value = gBattleMoveDamage;
value *= sExperienceScalingFactors[(gBattleMons[gBattlerFainted].level * 2) + 10];
value /= sExperienceScalingFactors[gBattleMons[gBattlerFainted].level + GetMonData(&gPlayerParty[gBattleStruct->expGetterMonId], MON_DATA_LEVEL) + 10];
gBattleMoveDamage = value + 1;
}
#endif
#if B_AFFECTION_MECHANICS == TRUE
if (GetBattlerFriendshipScore(gBattleStruct->expGetterMonId) >= FRIENDSHIP_50_TO_99)
gBattleMoveDamage = (gBattleMoveDamage * 120) / 100;
#endif
if (IsTradedMon(&gPlayerParty[gBattleStruct->expGetterMonId])) if (IsTradedMon(&gPlayerParty[gBattleStruct->expGetterMonId]))
{ {
@ -4244,7 +4229,16 @@ static void Cmd_getexp(void)
PREPARE_STRING_BUFFER(gBattleTextBuff2, i); PREPARE_STRING_BUFFER(gBattleTextBuff2, i);
PREPARE_WORD_NUMBER_BUFFER(gBattleTextBuff3, 6, gBattleMoveDamage); PREPARE_WORD_NUMBER_BUFFER(gBattleTextBuff3, 6, gBattleMoveDamage);
PrepareStringBattle(STRINGID_PKMNGAINEDEXP, gBattleStruct->expGetterBattlerId); if (gBattleStruct->sentInPokes & 1)
{
PrepareStringBattle(STRINGID_PKMNGAINEDEXP, gBattleStruct->expGetterBattlerId);
}
else if (!IsValidForBattle(&gPlayerParty[gBattleStruct->expGetterMonId+1]))
{
gLastUsedItem = ITEM_EXP_SHARE;
PrepareStringBattle(STRINGID_TEAMGAINEDEXP, 0);
}
MonGainEVs(&gPlayerParty[gBattleStruct->expGetterMonId], gBattleMons[gBattlerFainted].species); MonGainEVs(&gPlayerParty[gBattleStruct->expGetterMonId], gBattleMons[gBattlerFainted].species);
} }
gBattleStruct->sentInPokes >>= 1; gBattleStruct->sentInPokes >>= 1;
@ -16089,6 +16083,39 @@ u8 GetFirstFaintedPartyIndex(u8 battler)
return PARTY_SIZE; return PARTY_SIZE;
} }
void ApplyExperienceMultipliers(s32 *expAmount, u8 expGetterMonId, u8 faintedBattler)
{
u16 item = GetMonData(&gPlayerParty[expGetterMonId], MON_DATA_HELD_ITEM);
u8 holdEffect;
if (item == ITEM_ENIGMA_BERRY_E_READER)
holdEffect = gSaveBlock1Ptr->enigmaBerry.holdEffect;
else
holdEffect = ItemId_GetHoldEffect(item);
if (holdEffect == HOLD_EFFECT_LUCKY_EGG)
*expAmount = (*expAmount * 150) / 100;
if (B_TRAINER_EXP_MULTIPLIER <= GEN_7 && gBattleTypeFlags & BATTLE_TYPE_TRAINER)
*expAmount = (*expAmount * 150) / 100;
if (B_AFFECTION_MECHANICS == TRUE && GetBattlerFriendshipScore(expGetterMonId) >= FRIENDSHIP_50_TO_99)
*expAmount = (*expAmount * 120) / 100;
if (IsTradedMon(&gPlayerParty[expGetterMonId]))
*expAmount = (*expAmount * 150) / 100;
if (B_SCALED_EXP >= GEN_5 && B_SCALED_EXP != GEN_6)
{
// Note: There is an edge case where if a pokemon receives a large amount of exp, it wouldn't be properly calculated
// because of multiplying by scaling factor(the value would simply be larger than an u32 can hold). Hence u64 is needed.
u64 value = *expAmount;
u8 faintedLevel = gBattleMons[faintedBattler].level;
u8 expGetterLevel = GetMonData(&gPlayerParty[expGetterMonId], MON_DATA_LEVEL);
value *= sExperienceScalingFactors[(faintedLevel * 2) + 10];
value /= sExperienceScalingFactors[faintedLevel + expGetterLevel + 10];
*expAmount = value + 1;
}
}
void BS_ItemRestoreHP(void) void BS_ItemRestoreHP(void)
{ {
NATIVE_ARGS(); NATIVE_ARGS();

View File

@ -11082,3 +11082,12 @@ bool32 IsAlly(u32 battlerAtk, u32 battlerDef)
{ {
return (GetBattlerSide(battlerAtk) == GetBattlerSide(battlerDef)); return (GetBattlerSide(battlerAtk) == GetBattlerSide(battlerDef));
} }
bool32 IsGen6ExpShareEnabled(void)
{
#if I_EXP_SHARE_ITEM < GEN_6
return FALSE;
#else
return FlagGet(I_EXP_SHARE_FLAG);
#endif
}

View File

@ -6102,9 +6102,9 @@ const struct Item gItems[] =
.price = 3000, .price = 3000,
.holdEffect = HOLD_EFFECT_EXP_SHARE, .holdEffect = HOLD_EFFECT_EXP_SHARE,
.description = sExpShareDesc, .description = sExpShareDesc,
.pocket = POCKET_ITEMS, .pocket = I_EXP_SHARE_ITEM >= GEN_6 ? POCKET_KEY_ITEMS : POCKET_ITEMS,
.type = ITEM_USE_BAG_MENU, .type = ITEM_USE_FIELD,
.fieldUseFunc = ItemUseOutOfBattle_CannotUse, .fieldUseFunc = ItemUseOutOfBattle_ExpShare,
.flingPower = 30, .flingPower = 30,
}, },

View File

@ -2406,9 +2406,15 @@ static const u8 sWhiteHerbDesc[] = _(
"lowered stat."); "lowered stat.");
static const u8 sExpShareDesc[] = _( static const u8 sExpShareDesc[] = _(
#if I_EXP_SHARE_ITEM >= GEN_6
"This device gives\n"
"exp. to other\n"
"party members.");
#else
"A hold item that\n" "A hold item that\n"
"gets Exp. points\n" "gets Exp. points\n"
"from battles."); "from battles.");
#endif
static const u8 sQuickClawDesc[] = _( static const u8 sQuickClawDesc[] = _(
"A hold item that\n" "A hold item that\n"

View File

@ -200,6 +200,33 @@ void ItemUseOutOfBattle_Mail(u8 taskId)
Task_FadeAndCloseBagMenu(taskId); Task_FadeAndCloseBagMenu(taskId);
} }
STATIC_ASSERT(I_EXP_SHARE_ITEM < GEN_6 || I_EXP_SHARE_FLAG > TEMP_FLAGS_END, YouNeedToSetAFlagToUseGen6ExpShare);
void ItemUseOutOfBattle_ExpShare(u8 taskId)
{
#if I_EXP_SHARE_ITEM >= GEN_6
if (IsGen6ExpShareEnabled())
{
PlaySE(SE_PC_OFF);
if (!gTasks[taskId].data[2]) // to account for pressing select in the overworld
DisplayItemMessageOnField(taskId, gText_ExpShareOff, Task_CloseCantUseKeyItemMessage);
else
DisplayItemMessage(taskId, 1, gText_ExpShareOff, CloseItemMessage);
}
else
{
PlaySE(SE_EXP_MAX);
if (!gTasks[taskId].data[2]) // to account for pressing select in the overworld
DisplayItemMessageOnField(taskId, gText_ExpShareOn, Task_CloseCantUseKeyItemMessage);
else
DisplayItemMessage(taskId, 1, gText_ExpShareOn, CloseItemMessage);
}
FlagToggle(I_EXP_SHARE_FLAG);
#else
DisplayDadsAdviceCannotUseItemMessage(taskId, gTasks[taskId].tUsingRegisteredKeyItem);
#endif
}
void ItemUseOutOfBattle_Bike(u8 taskId) void ItemUseOutOfBattle_Bike(u8 taskId)
{ {
s16 *data = gTasks[taskId].data; s16 *data = gTasks[taskId].data;

View File

@ -1831,3 +1831,5 @@ const u8 gText_Answer[] = _("ANSWER");
const u8 gText_PokeBalls[] = _("POKé BALLS"); const u8 gText_PokeBalls[] = _("POKé BALLS");
const u8 gText_Berry[] = _("BERRY"); const u8 gText_Berry[] = _("BERRY");
const u8 gText_Berries[] = _("BERRIES"); const u8 gText_Berries[] = _("BERRIES");
const u8 gText_ExpShareOn[] = _("The Exp. Share has been turned on.{PAUSE_UNTIL_PRESS}");
const u8 gText_ExpShareOff[] = _("The Exp. Share has been turned off.{PAUSE_UNTIL_PRESS}");