Table of Contents
Credit to BluRose for writing this in pokeruby, Lunos for porting this to pokeemerald, and ExpoSeed for modifying it.
THIS TUTORIAL IS NOT COMPLETE YET, DO NOT USE IT YET
This tutorial will add the new Exp All mechanics from Gen 6. This entails making it a toggleable key item, modifying the associated text, distributing all the exp in battle, and displaying the proper message.
Contents
1. Edit behavior out of battle
First, we will need some new strings.
Add these declarations to include/strings.h:
extern const u8 gText_ExpShareOn[];
extern const u8 gText_ExpShareOff[];
And define it as such in src/strings.h:
const u8 gText_ExpShareOn[] = _("Turned on the Exp. Share.\pParty will now gain a portion\nof the Experience Points.{PAUSE_UNTIL_PRESS}");
const u8 gText_ExpShareOff[] = _("Turned off the Exp. Share.\pParty will no longer gain a portion\nof any Experience Points.{PAUSE_UNTIL_PRESS}");
We also need to change the Exp Share's item description. Edit src/data/text/item_descriptions.h:
static const u8 sExpShareDesc[] = _(
- "A hold item that\n"
- "gets EXP. points\n"
- "from battles.");
+ "A device that\n"
+ "shares EXP. points\n"
+ "to the party.");
Now that out of battle strings are taken care of, let's work on the item properties. Edit src/data/items.h:
[ITEM_EXP_SHARE] =
{
.name = _("EXP. SHARE"),
.itemId = ITEM_EXP_SHARE,
- .price = 3000,
+ .price = 0,
.holdEffect = HOLD_EFFECT_EXP_SHARE,
.description = sExpShareDesc,
- .pocket = POCKET_ITEMS,
- .type = 4,
- .fieldUseFunc = ItemUseOutOfBattle_CannotUse,
+ .importance = 1,
+ .pocket = POCKET_KEY_ITEMS,
+ .type = 2,
+ .fieldUseFunc = ItemUseOutOfBattle_ExpShare,
.secondaryId = 0,
},
We've changed the pocket, set the type to be the same type as other key items (this value only gets read by specific field callbacks like medicines, stones, and the bike anyways, so it's irrelevant), given the item an importance of 1 (so it cannot be tossed or deposited in the PC), and also gave it a function for field use. We will define the field use function next.
Edit include/global.h:
struct SaveBlock2
{
/*0x00*/ u8 playerName[PLAYER_NAME_LENGTH + 1];
/*0x08*/ u8 playerGender; // MALE, FEMALE
/*0x09*/ u8 specialSaveWarpFlags;
/*0x0A*/ u8 playerTrainerId[TRAINER_ID_LENGTH];
/*0x0E*/ u16 playTimeHours;
/*0x10*/ u8 playTimeMinutes;
/*0x11*/ u8 playTimeSeconds;
/*0x12*/ u8 playTimeVBlanks;
/*0x13*/ u8 optionsButtonMode; // OPTIONS_BUTTON_MODE_[NORMAL/LR/L_EQUALS_A]
/*0x14*/ u16 optionsTextSpeed:3; // OPTIONS_TEXT_SPEED_[SLOW/MID/FAST]
u16 optionsWindowFrameType:5; // Specifies one of the 20 decorative borders for text boxes
u16 optionsSound:1; // OPTIONS_SOUND_[MONO/STEREO]
u16 optionsBattleStyle:1; // OPTIONS_BATTLE_STYLE_[SHIFT/SET]
u16 optionsBattleSceneOff:1; // whether battle animations are disabled
u16 regionMapZoom:1; // whether the map is zoomed in
+ u16 expShare:1;
/*0x18*/ struct Pokedex pokedex;
/*0x90*/ u8 filler_90[0x8];
/*0x98*/ struct Time localTimeOffset;
/*0xA0*/ struct Time lastBerryTreeUpdate;
/*0xA8*/ u32 gcnLinkFlags; // Read by Pokemon Colosseum/XD
/*0xAC*/ u32 encryptionKey;
/*0xB0*/ struct PlayersApprentice playerApprentice;
/*0xDC*/ struct Apprentice apprentices[APPRENTICE_COUNT];
/*0x1EC*/ struct BerryCrush berryCrush;
/*0x1FC*/ struct PokemonJumpResults pokeJump;
/*0x20C*/ struct BerryPickingResults berryPick;
/*0x21C*/ struct RankingHall1P hallRecords1P[HALL_FACILITIES_COUNT][2][3]; // From record mixing.
/*0x57C*/ struct RankingHall2P hallRecords2P[2][3]; // From record mixing.
/*0x624*/ u16 contestLinkResults[5][4]; // 4 positions for 5 categories.
/*0x64C*/ struct BattleFrontier frontier;
}; // sizeof=0xF2C
Declare a new function in include/item_use.h:
void ItemUseOutOfBattle_ExpShare(u8);
Insert this function into src/item_use.c:
void ItemUseOutOfBattle_ExpShare(u8 taskId)
{
if (!gSaveBlock2Ptr->expShare)
{
PlaySE(SE_EXPMAX);
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, BagMenu_InitListsMenu);
}
}
else
{
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, BagMenu_InitListsMenu);
}
}
gSaveBlock2Ptr->expShare = !gSaveBlock2Ptr->expShare;
}
This function will toggle expShare
on or off and display the appropriate message. It is also possible to register the item, so we need to account for that as well.
Now, all our changes out of battle are complete. In the next step, we will focus on having the new sharing functionality work properly.
2. Edit behavior in battle
First, we will need some new text for the new Exp. Share. We need to define a new id. Edit include/constants/battle_string_ids.h:
-#define BATTLESTRINGS_COUNT 369
+#define BATTLESTRINGS_COUNT 370
+#define STRINGID_TEAMGAINEDEXP 381
Add this to src/battle_message.c. This can be done anywhere as long as it is before gBattleStringsTable
:
static const u8 sText_TeamGainedEXP[] = _("The rest of the team gained EXP. Points\nthanks to the EXP. SHARE!\p");
Then add this to gBattleStringsTable
:
[STRINGID_PKMNBOXLANETTESPCFULL - 12] = gText_PkmnTransferredLanettesPCBoxFull,
[STRINGID_TRAINER1WINTEXT - 12] = sText_Trainer1WinText,
[STRINGID_TRAINER2WINTEXT - 12] = sText_Trainer2WinText,
+ [STRINGID_TEAMGAINEDEXP - 12] = sText_TeamGainedEXP,
};
Now, the rest of our changes will be in Cmd_getexp
of src/battle_script_commands.c:
case 1: // calculate experience points to redistribute
{
u16 calculatedExp;
s32 viaSentIn;
for (viaSentIn = 0, i = 0; i < PARTY_SIZE; i++)
{
if (GetMonData(&gPlayerParty[i], MON_DATA_SPECIES) == SPECIES_NONE || GetMonData(&gPlayerParty[i], MON_DATA_HP) == 0)
continue;
if (gBitTable[i] & sentIn)
viaSentIn++;
item = GetMonData(&gPlayerParty[i], MON_DATA_HELD_ITEM);
if (item == ITEM_ENIGMA_BERRY)
holdEffect = gSaveBlock1Ptr->enigmaBerry.holdEffect;
else
holdEffect = ItemId_GetHoldEffect(item);
if (holdEffect == HOLD_EFFECT_EXP_SHARE)
viaExpShare++;
}
calculatedExp = gBaseStats[gBattleMons[gBattlerFainted].species].expYield * gBattleMons[gBattlerFainted].level / 7;
if (viaExpShare) // at least one mon is getting exp via exp share
{
*exp = calculatedExp / 2 / viaSentIn;
if (*exp == 0)
*exp = 1;
gExpShareExp = calculatedExp / 2 / viaExpShare;
if (gExpShareExp == 0)
gExpShareExp = 1;
}
else
{
*exp = calculatedExp / viaSentIn;
if (*exp == 0)
*exp = 1;
gExpShareExp = 0;
}
gBattleScripting.getexpState++;
gBattleStruct->expGetterMonId = 0;
gBattleStruct->sentInPokes = sentIn;
}
// fall through
case 2: // set exp value to the poke in expgetter_id and print message
if (gBattleControllerExecFlags == 0)
{
item = GetMonData(&gPlayerParty[gBattleStruct->expGetterMonId], MON_DATA_HELD_ITEM);
if (item == ITEM_ENIGMA_BERRY)
holdEffect = gSaveBlock1Ptr->enigmaBerry.holdEffect;
else
holdEffect = ItemId_GetHoldEffect(item);
if (holdEffect != HOLD_EFFECT_EXP_SHARE && !(gBattleStruct->sentInPokes & 1))
{
*(&gBattleStruct->sentInPokes) >>= 1;
gBattleScripting.getexpState = 5;
gBattleMoveDamage = 0; // used for exp
}
else if (GetMonData(&gPlayerParty[gBattleStruct->expGetterMonId], MON_DATA_LEVEL) == MAX_LEVEL)
{
*(&gBattleStruct->sentInPokes) >>= 1;
gBattleScripting.getexpState = 5;
gBattleMoveDamage = 0; // used for exp
}
else
{
// music change in wild battle after fainting a poke
if (!(gBattleTypeFlags & BATTLE_TYPE_TRAINER) && gBattleMons[0].hp && !gBattleStruct->wildVictorySong)
{
BattleStopLowHpSound();
PlayBGM(MUS_KACHI2);
gBattleStruct->wildVictorySong++;
}
if (GetMonData(&gPlayerParty[gBattleStruct->expGetterMonId], MON_DATA_HP))
{
if (gBattleStruct->sentInPokes & 1)
gBattleMoveDamage = *exp;
else
gBattleMoveDamage = 0;
if (holdEffect == HOLD_EFFECT_EXP_SHARE)
gBattleMoveDamage += gExpShareExp;
if (holdEffect == HOLD_EFFECT_LUCKY_EGG)
gBattleMoveDamage = (gBattleMoveDamage * 150) / 100;
if (gBattleTypeFlags & BATTLE_TYPE_TRAINER)
gBattleMoveDamage = (gBattleMoveDamage * 150) / 100;