diff --git a/include/battle_util.h b/include/battle_util.h index 81b0f8760..62394d85d 100644 --- a/include/battle_util.h +++ b/include/battle_util.h @@ -219,6 +219,7 @@ void CopyMonLevelAndBaseStatsToBattleMon(u32 battler, struct Pokemon *mon); void CopyMonAbilityAndTypesToBattleMon(u32 battler, struct Pokemon *mon); void RecalcBattlerStats(u32 battler, struct Pokemon *mon); bool32 IsAlly(u32 battlerAtk, u32 battlerDef); +bool32 IsGen6ExpShareEnabled(void); // Ability checks bool32 IsRolePlayBannedAbilityAtk(u16 ability); diff --git a/include/config/item.h b/include/config/item.h index ad205f003..8aaadbe84 100644 --- a/include/config/item.h +++ b/include/config/item.h @@ -16,6 +16,10 @@ // 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. +// 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 // 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. diff --git a/include/constants/battle_string_ids.h b/include/constants/battle_string_ids.h index 35d991bb8..dabb7d337 100644 --- a/include/constants/battle_string_ids.h +++ b/include/constants/battle_string_ids.h @@ -667,8 +667,9 @@ #define STRINGID_PKMNITEMMELTED 665 #define STRINGID_ULTRABURSTREACTING 666 #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. // String ids before this (e.g. STRINGID_INTROMSG) are not in the table, diff --git a/include/global.h b/include/global.h index 6a7e3d2cc..60abf094a 100644 --- a/include/global.h +++ b/include/global.h @@ -139,6 +139,8 @@ // It looks like file.c:line: size of array `id' is negative #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 { s8 x; diff --git a/include/item_use.h b/include/item_use.h index afa87c697..6b3d0c590 100644 --- a/include/item_use.h +++ b/include/item_use.h @@ -30,6 +30,7 @@ void ItemUseOutOfBattle_FormChange(u8); void ItemUseOutOfBattle_FormChange_ConsumedOnUse(u8); void ItemUseOutOfBattle_Honey(u8); void ItemUseOutOfBattle_CannotUse(u8); +void ItemUseOutOfBattle_ExpShare(u8); void ItemUseInBattle_BagMenu(u8 taskId); void ItemUseInBattle_PartyMenu(u8 taskId); void ItemUseInBattle_PartyMenuChooseMove(u8 taskId); diff --git a/include/strings.h b/include/strings.h index 4019dee0b..845e16e9f 100644 --- a/include/strings.h +++ b/include/strings.h @@ -3034,4 +3034,8 @@ extern const u8 gText_BoxName[]; extern const u8 gText_PkmnsNickname[]; extern const u8 gText_TellHimTheWords[]; +// Exp. Share +extern const u8 gText_ExpShareOn[]; +extern const u8 gText_ExpShareOff[]; + #endif // GUARD_STRINGS_H diff --git a/src/battle_message.c b/src/battle_message.c index c5972c548..5c86685e0 100644 --- a/src/battle_message.c +++ b/src/battle_message.c @@ -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_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_TeamGainedEXP[] = _("The rest of your team gained EXP.\nPoints thanks to the {B_LAST_ITEM}!\p"); const u8 *const gBattleStringsTable[BATTLESTRINGS_COUNT] = { @@ -1462,6 +1462,7 @@ const u8 *const gBattleStringsTable[BATTLESTRINGS_COUNT] = [STRINGID_PKMNFROSTBITEHEALEDBY - BATTLESTRINGS_TABLE_START] = sText_PkmnFrostbiteHealedBy, [STRINGID_ULTRABURSTREACTING - BATTLESTRINGS_TABLE_START] = sText_UltraBurstReacting, [STRINGID_ULTRABURSTCOMPLETED - BATTLESTRINGS_TABLE_START] = sText_UltraBurstCompleted, + [STRINGID_TEAMGAINEDEXP - BATTLESTRINGS_TABLE_START] = sText_TeamGainedEXP, }; const u16 gTrainerUsedItemStringIds[] = diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 2aed36fb9..c2618afb4 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -354,6 +354,7 @@ static void BestowItem(u32 battlerAtk, u32 battlerDef); static bool8 IsFinalStrikeEffect(u16 move); static void TryUpdateRoundTurnOrder(void); static bool32 ChangeOrderTargetAfterAttacker(void); +void ApplyExperienceMultipliers(s32 *expAmount, u8 expGetterMonId, u8 faintedBattler); static void Cmd_attackcanceler(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) { CMD_ARGS(u8 battler); @@ -4088,7 +4091,7 @@ static void Cmd_getexp(void) else holdEffect = ItemId_GetHoldEffect(item); - if (holdEffect == HOLD_EFFECT_EXP_SHARE) + if (holdEffect == HOLD_EFFECT_EXP_SHARE || IsGen6ExpShareEnabled()) viaExpShare++; } #if (B_SCALED_EXP >= GEN_5) && (B_SCALED_EXP != GEN_6) @@ -4137,7 +4140,8 @@ static void Cmd_getexp(void) else 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; gBattleScripting.getexpState = 5; @@ -4174,34 +4178,15 @@ static void Cmd_getexp(void) else gBattleMoveDamage = 0; - // only give exp share bonus in later gens if the mon wasn't sent out - #if B_SPLIT_EXP < GEN_6 - if (holdEffect == HOLD_EFFECT_EXP_SHARE) + if ((holdEffect == HOLD_EFFECT_EXP_SHARE || IsGen6ExpShareEnabled()) +#if B_SPLIT_EXP >= GEN_6 + // only give exp share bonus in later gens if the mon wasn't sent out + && gBattleMoveDamage == 0 +#endif + ) gBattleMoveDamage += gExpShareExp; - #else - if (holdEffect == HOLD_EFFECT_EXP_SHARE && gBattleMoveDamage == 0) - 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 + + ApplyExperienceMultipliers(&gBattleMoveDamage, gBattleStruct->expGetterMonId, gBattlerFainted); if (IsTradedMon(&gPlayerParty[gBattleStruct->expGetterMonId])) { @@ -4244,7 +4229,16 @@ static void Cmd_getexp(void) PREPARE_STRING_BUFFER(gBattleTextBuff2, i); 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); } gBattleStruct->sentInPokes >>= 1; @@ -16089,6 +16083,39 @@ u8 GetFirstFaintedPartyIndex(u8 battler) 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) { NATIVE_ARGS(); diff --git a/src/battle_util.c b/src/battle_util.c index e0ac7c2a6..d4e873b7e 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -11106,3 +11106,12 @@ bool32 IsAlly(u32 battlerAtk, u32 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 +} diff --git a/src/data/items.h b/src/data/items.h index 6d293f189..3154671cf 100644 --- a/src/data/items.h +++ b/src/data/items.h @@ -6102,9 +6102,9 @@ const struct Item gItems[] = .price = 3000, .holdEffect = HOLD_EFFECT_EXP_SHARE, .description = sExpShareDesc, - .pocket = POCKET_ITEMS, - .type = ITEM_USE_BAG_MENU, - .fieldUseFunc = ItemUseOutOfBattle_CannotUse, + .pocket = I_EXP_SHARE_ITEM >= GEN_6 ? POCKET_KEY_ITEMS : POCKET_ITEMS, + .type = ITEM_USE_FIELD, + .fieldUseFunc = ItemUseOutOfBattle_ExpShare, .flingPower = 30, }, diff --git a/src/data/text/item_descriptions.h b/src/data/text/item_descriptions.h index ecca3ef30..0948fbe18 100644 --- a/src/data/text/item_descriptions.h +++ b/src/data/text/item_descriptions.h @@ -2406,9 +2406,15 @@ static const u8 sWhiteHerbDesc[] = _( "lowered stat."); 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" "gets Exp. points\n" "from battles."); +#endif static const u8 sQuickClawDesc[] = _( "A hold item that\n" diff --git a/src/item_use.c b/src/item_use.c index 3ca6df6cd..9c38e8212 100644 --- a/src/item_use.c +++ b/src/item_use.c @@ -200,6 +200,33 @@ void ItemUseOutOfBattle_Mail(u8 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) { s16 *data = gTasks[taskId].data; diff --git a/src/strings.c b/src/strings.c index eb7bb8ba1..f9137c4f1 100644 --- a/src/strings.c +++ b/src/strings.c @@ -1831,3 +1831,5 @@ const u8 gText_Answer[] = _("ANSWER"); const u8 gText_PokeBalls[] = _("POKé BALLS"); const u8 gText_Berry[] = _("BERRY"); 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}");