Simple Modifications Directory

Started by Hiroshi Sotomura December 30th, 2018 2:48 PM
  • 223279 views
  • 464 replies

Hiroshi Sotomura

Ashe – House of Blue Lions

Male
Melbourne, Australia
Seen 1 Day Ago
Posted 3 Weeks Ago
18,639 posts
20.5 Years

Decomp Simple Modifications Directory


This thread is a collection of handy modifications for pokeemerald, pokeruby and pokefirered. Modifications tend to range in both size and scope and cover all different facets of the games. The goal of this collection is to share resources among all fan-game developers while also showcasing to newer developers the ease and benefits of switching to decompilation-based projects.

List of Tutorials / Modifications


For ease of maintenance, the list of tutorials is now listed on the pokeemerald wiki. If you notice a tutorial missing from that list, feel free to edit and add it yourself!

Before submitting


  1. PLEASE make sure your implementation works in an unchanged/vanilla version of the game.
  2. PLEASE try formatting your tutorial for ease of reading. A good format to follow can be seen in Show Type Effectiveness In-Battle by PokemonCrazy.
  3. Choose a place to submit your tutorial. GitHub is preferred, but if you can't use GitHub, you can create a new post in this thread.

After submitting


  1. Please add your tutorial to the list on github. (Or ask somebody who can, like psf on pokecommunity, or members of #pokeemerald in pret.) Adding to the index will help other developers find it quickly and easily.
  2. Publicize your work! Post about it on reddit, Twitter, or discord communities like #pokeemerald in pret or #decomps-disassemblies in PokéCommunity. Spread the good word, & thanks for the contribution! ☺️

Avara

She/Her
Seen 3 Weeks Ago
Posted April 10th, 2022
1,305 posts
11.5 Years
Editing Player PC Items

To change the items the player has in their PC at the start of the game, open “src\player_pc.c” in Notepad++ and find the following:



Ctrl+F "POTION" to locate it quickly. It's self-explanatory - on the left you have the item and on the right you have the amount. Just make sure you don't accidentally overwrite the “{ ITEM_NONE, 0 }” line. For example, if I wanted my player to have five Sitrus Berries and three Lum Berries in their PC at the beginning of the game, I'd have:



A list of items can be found in “include\constants\items.h

Avara

She/Her
Seen 3 Weeks Ago
Posted April 10th, 2022
1,305 posts
11.5 Years
Modifying Starter Pokémon

To change the three starters in less than 30 seconds, open “src\starter_choose.c” in Notepad++ and Ctrl+F "TREECKO" to find the following section quickly:



You've probably guessed what to do already - just change the species names. If you wanted the Johto starters, you could have:



Compile and you're all set.

Avara

She/Her
Seen 3 Weeks Ago
Posted April 10th, 2022
1,305 posts
11.5 Years
Changing the Beginning Amount of Money

For Pokéemerald, open “src\new_game.c” and look for this line:
SetMoney(&gSaveBlock1Ptr->money, 3000);
All there is to do is change that amount (3000) to whatever you like. For Pokéruby, it's the same file but this line:
gSaveBlock1.money = 3000;

Avara

She/Her
Seen 3 Weeks Ago
Posted April 10th, 2022
1,305 posts
11.5 Years
Editing Default Options Settings

If you open “src\new_game.c” you'll come across this:



These are the attributes you can change from the "Options" window in the start menu - text speed, the menu frame, battle style (set/shift) etc. Having the text speed automatically set to fast is something I'd recommend to everyone, since going straight to the options menu and changing it is one of the first things a lot of people do in-game!

Avara

She/Her
Seen 3 Weeks Ago
Posted April 10th, 2022
1,305 posts
11.5 Years
Changing Birch's Intro Pokémon

Open “src\main_menu.c” and Ctrl+F for “SPECIES_LOTAD”. Modify the following lines to change the sprite and cry:





The Pokémon species list is in “include\constants\species.h”. For Pokéruby, substitute the instances of “SPECIES_LOTAD” for “SPECIES_AZURILL”. I've changed it to Trapinch, for example:

Male
'Straya
Seen 7 Hours Ago
Posted 3 Weeks Ago
38 posts
14 Years
Reusable TMs

To make TMs reusable open "src/party_menu.c" and search for the sub_81B6EB4 function.
In it delete the lines
if (item < ITEM_HM01_CUT)
    RemoveBagItem(item, 1);
If you also want to make TMs unholdable and remove the number from the bag saying how many of each TM you have open "src/data/items.h" and change the .importance number of each TM item from 0 to 1.

Avara

She/Her
Seen 3 Weeks Ago
Posted April 10th, 2022
1,305 posts
11.5 Years
Modifying the Start Location

Open “src\new_game.c”and look for the following section:
static void WarpToTruck(void)
{
    SetWarpDestination(MAP_GROUP(INSIDE_OF_TRUCK), MAP_NUM(INSIDE_OF_TRUCK), -1, -1, -1);
    WarpIntoMap();
}
The line in red is the one we want to make changes to as you could probably already tell. It's self explanatory so it shouldn't need very much breaking down. For instance, let's say we wanted the start map to be Oldale Town. We would have:
SetWarpDestination(MAP_GROUP(OLDALE_TOWN), MAP_NUM(OLDALE_TOWN), -1, 6, 18);
The above will have the player start in Oldale Town, specifically outside the Pokémon Center. -1 is used for no warp ID, 6, 18 are co-ordinates. Something different, say you wanted the player to start off in the middle of May's room:
SetWarpDestination(MAP_GROUP(LITTLEROOT_TOWN_MAYS_HOUSE_2F), MAP_NUM(LITTLEROOT_TOWN_MAYS_HOUSE_2F), -1, -1, -1);
Using “-1, -1, -1” as the default does will place the player in the center of the map. That's it!
Oh and to remove the truck animation, again you only have to tweak a single line, this time in “src\overworld.c”:
// gFieldCallback = ExecuteTruckSequence;

DizzyEgg

Age 25
Male
Poland
Seen March 23rd, 2023
Posted April 23rd, 2020
794 posts
9.3 Years
BW Repel

Whenever the Repel's effect wears off, a prompt will appear asking player if they wish to use another one. Similarly to my old pre-decomp implementation, this one also lets you choose which Repel(Regular, Super, Max) to use. Obviously, if you have only one kind, this part is skipped.

src/script_menu.c DrawMultichoiceMenu
I modified the function, so it works with custom menu options, in ours case Repels ones. Change that part to:
Spoiler:
static void DrawMultichoiceMenuCustom(u8 left, u8 top, u8 multichoiceId, u8 ignoreBPress, u8 cursorPos, const struct MenuAction *actions, int count)
{
    int i, windowId, width = 0;
    u8 newWidth;

    for (i = 0; i < count; i++)
    {
        width = DisplayTextAndGetWidth(actions[i].text, width);
    }

    newWidth = ConvertPixelWidthToTileWidth(width);
    left = ScriptMenu_AdjustLeftCoordFromWidth(left, newWidth);
    windowId = CreateWindowFromRect(left, top, newWidth, count * 2);
    SetStandardWindowBorderStyle(windowId, 0);
    PrintMenuTable(windowId, count, actions);
    InitMenuInUpperLeftCornerPlaySoundWhenAPressed(windowId, count, cursorPos);
    schedule_bg_copy_tilemap_to_vram(0);
    InitMultichoiceCheckWrap(ignoreBPress, count, windowId, multichoiceId);
}

static void DrawMultichoiceMenu(u8 left, u8 top, u8 multichoiceId, u8 ignoreBPress, u8 cursorPos)
{
    DrawMultichoiceMenuCustom(left, top, multichoiceId, ignoreBPress, cursorPos, sMultichoiceLists[multichoiceId].list, sMultichoiceLists[multichoiceId].count);
}

void TryDrawRepelMenu(void)
{
    static const u16 repelItems[] = {ITEM_REPEL, ITEM_SUPER_REPEL, ITEM_MAX_REPEL};
    struct MenuAction menuItems[4] = {NULL};
    int i, count = 0;

    for (i = 0; i < ARRAY_COUNT(repelItems); i++)
    {
        if (CheckBagHasItem(repelItems[i], 1))
        {
            VarSet(VAR_0x8004 + count, repelItems[i]);
            menuItems[count].text = ItemId_GetName(repelItems[i]);
            count++;
        }
    }

    if (count > 1)
        DrawMultichoiceMenuCustom(0, 0, 0, FALSE, 0, menuItems, count);

    gSpecialVar_Result = (count > 1);
}

void HandleRepelMenuChoice(void)
{
    gSpecialVar_0x8004 = VarGet(VAR_0x8004 + gSpecialVar_Result); // Get item Id;
    VarSet(VAR_REPEL_STEP_COUNT, ItemId_GetHoldEffectParam(gSpecialVar_0x8004));
}

Now
data/scripts/repel.inc
Spoiler:
EventScript_RepelWoreOff:: @ 82A4B2A
	msgbox Text_RepelWoreOff, MSGBOX_SIGN
	checkitem ITEM_REPEL, 1
	compare VAR_RESULT, TRUE
	goto_if_eq EventScript_RepelWoreOff_AskAnother
	checkitem ITEM_SUPER_REPEL, 1
	compare VAR_RESULT, TRUE
	goto_if_eq EventScript_RepelWoreOff_AskAnother
	checkitem ITEM_MAX_REPEL, 1
	compare VAR_RESULT, FALSE
	goto_if_eq EventScript_RepelWoreOff_End
EventScript_RepelWoreOff_AskAnother:
	msgbox Text_RepelAskAnother, MSGBOX_YESNO
	closemessage
	compare VAR_RESULT, 0
	goto_if_eq EventScript_RepelWoreOff_End
	callnative TryDrawRepelMenu
	compare VAR_RESULT, FALSE
	goto_if_eq EventScript_RepelWoreOff_Chose
	waitstate
	compare VAR_RESULT, 127
	goto_if_eq EventScript_RepelWoreOff_End
EventScript_RepelWoreOff_Chose:
	callnative HandleRepelMenuChoice
	bufferitemname 1, VAR_0x8004
	removeitem VAR_0x8004, 1
	playse SE_TU_SAA
	msgbox gText_PlayerUsedVar2, MSGBOX_SIGN
EventScript_RepelWoreOff_End:
	end

Text_RepelWoreOff: @ 82A4B33
	.string "REPEL's effect wore off…$"
Text_RepelAskAnother:
	.string "Would you like to use another one?$"

Screenshots:
Spoiler:



I also have the code in my github branch called repel, so you can pull directly from it.
Support Pokeemerald!

Pokeemerald starter pack:
Emerald Expansion

DizzyEgg

Age 25
Male
Poland
Seen March 23rd, 2023
Posted April 23rd, 2020
794 posts
9.3 Years
Money Limit
Spoiler:

In this example, I'll be moving the cap to 9 999 999.

To change max possible money amount, go to
src/money.c
At line 13,
#define MAX_MONEY 9999999
At line 135
PrintMoneyAmount(windowId, 0x20, 1, amount, speed);
At line 143
ConvertIntToDecimalStringN(gStringVar1, amount, STR_CONV_MODE_LEFT_ALIGN, 7);
At line 145
strLength = 7 - StringLength(gStringVar1);
In
src/shop.c
At line 1148
PrintMoneyAmount(4, 32, 1, gShopDataPtr->totalCost, TEXT_SPEED_FF);
And in
src/trainer_card.c
At line 1014
ConvertIntToDecimalStringN(gStringVar1, sData->trainerCard.money, 0, 7);
Things that were changed were respectively, max money amount, x position on the money box and max digits(previously 6, now 7).

If you'd like to go above 7 digits, simply change the 7 to 8 and the x position argument from 0x20 to
0x1A.

Screenshots:
Spoiler:




I also have the code in my github branch called money, so you can pull directly from it.
Support Pokeemerald!

Pokeemerald starter pack:
Emerald Expansion

Lunos

Random Uruguayan User

Male
Montevideo (Uruguay)
Seen 7 Hours Ago
Posted 1 Day Ago
3,006 posts
14.7 Years
-Snip-
I'm not entirely sure if I'm going OffTopic here, but while this implementation works perfectly on itself, it introduces one issue.
You can teach a TM to a pokémon, and the PPs for that move slot will refill as the pokémon learns a new move.
This kinda removes the necessity of healing when/if you run out of PPs in some of all of your moves.

ON: Since we're talking about quick modifications, I have to give some love to my favourite ability.

Modifying the table of items of the Pickup ability (Em)
(outside of the Battle Pyramid)


To do this, we're going to open the src\battle_script_commands.c file.
Next, we'll take a look at the sPickupItems variable in the Line 824.
What it contains is a list of the items the Player can obtain with a 4%, 10% or 30% chances every time they faint a pokémon, if a Pickup user is in their party.


Right after it, we have the variable sRarePickupItems, which similarly, also contains a list of items.
These are the items that the Player has a 1% chance of obtaining, every time they faint a pokémon, if a Pickup user is in their party.


To modify any of these lists, you simply have to swap the defined item/s that you want to replace with a different one of your liking, save and compile.

Here's a quick video, where I replaced all the common items (those in the 1st list) with Master Balls.


I haven't looked into the separate table used for the Battle Pyramid, so I won't go into details with that one, but it looks fairly simple.
Any curious people can find them (Lv50 and Open) starting from the Line 317 of the
src/battle_pyramid.c
file.

DizzyEgg

Age 25
Male
Poland
Seen March 23rd, 2023
Posted April 23rd, 2020
794 posts
9.3 Years
Scrolling Multichoices In Scripts

I was asked to implement this, so here it goes. Just so you know, scrolling lists are already coded in, they're called list menus in pokeemerald. Check out list_menu.h and list_menu.c.

Spoiler:

I put the code in script_menu.c, but you can put it whenever you want.
At the top of file make sure to include the header for the list menu. Add this.
#include "list_menu.h"
At the end of the file paste all this code.
Spoiler:

// Text displayed as options.
static const u8 sText_Example1[] = _("Example 1");
static const u8 sText_Example2[] = _("Example 2");
static const u8 sText_Example3[] = _("Example 3");
static const u8 sText_Example4[] = _("Example 4");
static const u8 sText_Example5[] = _("Example 5");
static const u8 sText_Example6[] = _("Example 6");
static const u8 sText_Example7[] = _("Example 7");
static const u8 sText_Example8[] = _("Example 8");
static const u8 sText_Example9[] = _("Example 9");

// Sets of multichoices.
static const struct ListMenuItem sSet1[] =
{
    {sText_Example1, 0},
    {sText_Example2, 1},
    {sText_Example3, 2},
    {sText_Example4, 3},
    {sText_Example5, 4},
    {sText_Example6, 5},
    {sText_Example7, 6},
    {sText_Example8, 7},
    {sText_Example9, 8},
};

static const struct ListMenuItem sSet2[] =
{
    {sText_Example9, 0},
    {sText_Example8, 1},
    {sText_Example7, 2},
    {sText_Example6, 3},
    {sText_Example5, 4},
    {sText_Example4, 5},
    {sText_Example3, 6},
    {sText_Example2, 7},
    {sText_Example1, 8},
};

// Table of your multichoice sets.
struct
{
    const struct ListMenuItem *set;
    int count;
} static const sScrollingSets[] =
{
    {sSet1, ARRAY_COUNT(sSet1)},
    {sSet2, ARRAY_COUNT(sSet2)},
};

static void Task_ScrollingMultichoiceInput(u8 taskId);

static const struct ListMenuTemplate sMultichoiceListTemplate =
{
    .header_X = 0,
    .item_X = 8,
    .cursor_X = 0,
    .upText_Y = 1,
    .cursorPal = 2,
    .fillValue = 1,
    .cursorShadowPal = 3,
    .lettersSpacing = 1,
    .itemVerticalPadding = 0,
    .scrollMultiple = LIST_NO_MULTIPLE_SCROLL,
    .fontId = 1,
    .cursorKind = 0
};

// 0x8004 = set id
// 0x8005 = window X
// 0x8006 = window y
// 0x8007 = showed at once
// 0x8008 = Allow B press
void ScriptMenu_ScrollingMultichoice(void)
{
    int i, windowId, taskId, width = 0;
    int setId = gSpecialVar_0x8004;
    int left = gSpecialVar_0x8005;
    int top = gSpecialVar_0x8006;
    int maxShowed = gSpecialVar_0x8007;

    for (i = 0; i < sScrollingSets[setId].count; i++)
        width = display_text_and_get_width(sScrollingSets[setId].set[i].name, width);

    width = convert_pixel_width_to_tile_width(width);
    left = sub_80E2D5C(left, width);
    windowId = CreateWindowFromRect(left, top, width, maxShowed * 2);
    SetStandardWindowBorderStyle(windowId, 0);
    CopyWindowToVram(windowId, 3);

    gMultiuseListMenuTemplate = sMultichoiceListTemplate;
    gMultiuseListMenuTemplate.windowId = windowId;
    gMultiuseListMenuTemplate.items = sScrollingSets[setId].set;
    gMultiuseListMenuTemplate.totalItems = sScrollingSets[setId].count;
    gMultiuseListMenuTemplate.maxShowed = maxShowed;

    taskId = CreateTask(Task_ScrollingMultichoiceInput, 0);
    gTasks[taskId].data[0] = ListMenuInit(&gMultiuseListMenuTemplate, 0, 0);
    gTasks[taskId].data[1] = gSpecialVar_0x8008;
    gTasks[taskId].data[2] = windowId;
}

static void Task_ScrollingMultichoiceInput(u8 taskId)
{
    bool32 done = FALSE;
    s32 input = ListMenu_ProcessInput(gTasks[taskId].data[0]);

    switch (input)
    {
    case LIST_HEADER:
    case LIST_NOTHING_CHOSEN:
        break;
    case LIST_CANCEL:
        if (gTasks[taskId].data[1])
        {
            gSpecialVar_Result = 0x7F;
            done = TRUE;
        }
        break;
    default:
        gSpecialVar_Result = input;
        done = TRUE;
        break;
    }

    if (done)
    {
        DestroyListMenuTask(gTasks[taskId].data[0], NULL, NULL);
        ClearStdWindowAndFrame(gTasks[taskId].data[2], TRUE);
        RemoveWindow(gTasks[taskId].data[2]);
        EnableBothScriptContexts();
        DestroyTask(taskId);
    }
}

Then, add this macro at the end of asm/macros/event.inc.
Spoiler:
	.macro scrollingmultichoice id:req, x:req, y:req, show:req, b:req
	setvar VAR_0x8004, \id,
	setvar VAR_0x8005, \x,
	setvar VAR_0x8006, \y,
	setvar VAR_0x8007, \show,
	setvar VAR_0x8008, \b,
	callnative ScriptMenu_ScrollingMultichoice
	waitstate
	.endm

How to use it? I provided two example sets of multichoices. Let me explain.
Spoiler:

Under the // Text displayed as options. comment I put all the strings that are displayed as options. Now, you may put the strings at any location, but keep in mind if the file location is different, you'll need to extern them.
Spoiler:
static const u8 sText_Example1[] = _("Example 1");
static const u8 sText_Example2[] = _("Example 2");
static const u8 sText_Example3[] = _("Example 3");
static const u8 sText_Example4[] = _("Example 4");
static const u8 sText_Example5[] = _("Example 5");


Then, under the // Sets of multichoices. comment I put all the sets of multichoices. What I mean by that is simply the options the player will be able to choose from. The syntax is simple, string name and id. The id is basically what you'll get in gSpecialVar_Result after the player chooses.
Spoiler:
static const struct ListMenuItem sSet1[] =
{
    {sText_Example1, 0},
    {sText_Example2, 1},
    {sText_Example3, 2},
    {sText_Example4, 3},
    {sText_Example5, 4},
    {sText_Example6, 5},
    {sText_Example7, 6},
    {sText_Example8, 7},
    {sText_Example9, 8},
};


And finally we have // Table of your multichoice sets. comment. As you may guess, this is the table with pointers to the multichoice sets along with the information how many options there are. Syntax is simple, set name and the number of options to choose from(I used a handy macro and advise you to do the same.)
Spoiler:
struct
{
    const struct ListMenuItem *set;
    int count;
} static const sScrollingSets[] =
{
    {sSet1, ARRAY_COUNT(sSet1)},
    {sSet2, ARRAY_COUNT(sSet2)},
};

Calling it from a script is simple, just use the macro you just put. The arguments are:
- set id(sScrollingSets table)
- window x
- window y
- amount of options showed at once on window
- whether the player can press B(if TRUE, the return value is 0x7F just as from normal multichoice)
Whichever option the player chooses is stored in gSpecialVar_Result.

Example:
scrollingmultichoice 1, 2, 3, 5, TRUE




I also have the code in my github branch called script_multichoice, so you can pull directly from it.
Support Pokeemerald!

Pokeemerald starter pack:
Emerald Expansion
Seen 1 Week Ago
Posted February 18th, 2022
49 posts
4.5 Years
Running Indoors (EM)


Simply remove the following from /src/bike.c and you should be able to use your running shoes inside any building.

Inside the file search for:
bool32 IsRunningDisallowed(u8 metatile)
and delete the following:
!(gMapHeader.flags & MAP_ALLOW_RUNNING) ||
Thats it, now you should be good to go (or run in this case).


CREDIT:
None needed
Seen 1 Week Ago
Posted February 18th, 2022
49 posts
4.5 Years
OUTDATED Surviving poison outside of battle with 1hp

This no longer works, but Lunos has found a fix further down - go check it out!



With just a few steps your pokemon should survive poison on the overworld now with 1hp, like in later gens.

Open the file /src/field_poison.c
Spoiler:

Find the following function:
s32 DoPoisonFieldEffect(void)
Simply add:
u32 numSurvived = 0;
Then find this code inside:
if(hp == 0 || --hp == 0)
and change the whole if-statement to:
if (hp == 1 || --hp == 1)
{
	u32 status = STATUS1_NONE;
	SetMonData(pokemon, MON_DATA_STATUS, &status);
	numSurvived++;
	ScriptContext1_SetupScript(EventScript_PoisonSurvial);
}
Last in this file add the following after the for-loop:
if (numSurvived != 0)
{
	return FLDPSN_NONE;
}


Next open /data/event_scripts.s and add the following to the end of the file:
Spoiler:

EventScript_PoisonSurvial::
	msgbox Text_PoisonSurvial, MSGBOX_SIGN
Text_PoisonSurvial:
	.string "Poison survived with 1hp…$"


Thats it, everything should be done.
Seen March 20th, 2019
Posted March 19th, 2019
1 posts
4.9 Years
Skipping the poochyena battle after choosing the starter (pokéemerald)

You can skip the poochyena battle started by the script command "special ChooseStarter" by modifying a few lines in src/battle_setup.c.

In the CB2_GiveStarter function

Spoiler:
comment/delete the following line
PlayBattleBGM();
change the following line
BattleTransition_Start(B_TRANSITION_BLUR);
to
BeginNormalPaletteFade(0xFFFFFFFF, -1, 0, 0x10, 0);


In the CB2_StartFirstBattle function

Spoiler:
change the line
SetMainCallback2(CB2_InitBattle);
to
SetMainCallback2(CB2_EndFirstBattle);
and the line
if (IsBattleTransitionDone() == TRUE)
to
if (!gPaletteFade.active)


This modification also adds a graceful transition (a simple fade) to the overworld to avoid glitches. I didn't extensively test this, however since it only affects functions which are used once in the entire game, it should not have any unexpected side-effects.
Seen March 26th, 2023
Posted September 3rd, 2021
50 posts
5.9 Years
How to force the Battle style [Set|Shift] [EM]

This guide is for you when you want to force the player to play in set or shiftmode. For either the enteire game or just during important matches. ex: Always switch except during gyms matches were you force set.

Old tutorial:
Spoiler:

Most changes are done in the option_menu.c file.

To dissable the user from change the setting we are going to remove this setting from the option menu. We'll start by commenting (or deleting) some lines.
Spoiler:

24: TD_BATTLESTYLE,
35: MENUITEM_BATTLESTYLE,

60: static u8 BattleStyle_ProcessInput(u8 selection);

61: static void BattleStyle_DrawChoices(u8 selection);

84: gText_BattleStyle,

235: gTasks[taskId].data[TD_BATTLESTYLE] = gSaveBlock2Ptr->optionsBattleStyle;
242: BattleStyle_DrawChoices(gTasks[taskId].data[TD_BATTLESTYLE]);

313->319:
case MENUITEM_BATTLESTYLE: break;
/*previousOption = gTasks[taskId].data[TD_BATTLESTYLE];
gTasks[taskId].data[TD_BATTLESTYLE] = BattleStyle_ProcessInput(gTasks[taskId].data[TD_BATTLESTYLE]);

if (previousOption != gTasks[taskId].data[TD_BATTLESTYLE])
BattleStyle_DrawChoices(gTasks[taskId].data[TD_BATTLESTYLE]);
break;

358: gSaveBlock2Ptr->optionsBattleStyle = gTasks[taskId].data[TD_BATTLESTYLE];


Then comment/delete the whole functions: BattleStyle_ProcessInput and BattleStyle_DrawChoices arround lines 470->491

Now the user can't change this setting anymore, but the options menu will look weird, what we now need to do is lower a few magic numbers.

Spoiler:

At 282: gTasks[taskId].data[TD_MENUSELECTION] = 6;
to gTasks[taskId].data[TD_MENUSELECTION] = /*6*/5;

At 287: if (gTasks[taskId].data[TD_MENUSELECTION] <= 5)
to if (gTasks[taskId].data[TD_MENUSELECTION] <= /*5*/4)

Then in the drawchoice functions we need to lower some y-cord by 16

At 513: DrawOptionMenuChoice(gText_SoundMono, 104, 48, styles[0]);
DrawOptionMenuChoice(gText_SoundStereo, GetStringRightAlignXOffset(1, gText_SoundStereo, 198), 48, styles[1]);

to DrawOptionMenuChoice(gText_SoundMono, 104, /*48*/32, styles[0]);
DrawOptionMenuChoice(gText_SoundStereo, GetStringRightAlignXOffset(1, gText_SoundStereo, 198), /*48*/32, styles[1]);

At 571: DrawOptionMenuChoice(gText_FrameType, 104, 80, 0);
DrawOptionMenuChoice(text, 128, /*80*/64, 1);
DrawOptionMenuChoice(text, 128, 80, 1);


to DrawOptionMenuChoice(gText_FrameType, 104, /*80*/64, 0);
DrawOptionMenuChoice(text, 128, /*80*/64, 1);
DrawOptionMenuChoice(text, 128, /*80*/64, 1);

Then do the same in ButtonMode_DrawChoices(u8 selection)
Around 598 for 64->48

And FrameType_DrawChoices(u8 selection)
Around 544 for 80->64



To set the default mode go to new_game.c

Spoiler:

set around line 102: gSaveBlock2Ptr->optionsBattleStyle = OPTIONS_BATTLE_STYLE_SHIFT;
to OPTIONS_BATTLE_STYLE_SET; if you want the default style to be set.


Edit: I made a small repository for merging here.

To change this style change the gSaveBlock2Ptr->optionsBattleStyle.
Emerald master race

Delta231

A noob

Male
India
Seen February 1st, 2022
Posted November 22nd, 2019
682 posts
7.1 Years
Have PokeBalls loading unique images [EM]


While, I was making my DS-Styled Balls for my Hack I noticed that some Balls loaded same 3rd animation image and only their palette was unique. This modification is for those who have their PokeBalls having different image.

Go to src/pokeball.c and go to function LoadBallGfx. You will find a switch case which would be similar to this
Spoiler:

 u16 var;

    if (GetSpriteTileStartByTag(gBallSpriteSheets[ballId].tag) == 0xFFFF)
    {
        LoadCompressedSpriteSheetUsingHeap(&gBallSpriteSheets[ballId]);
        LoadCompressedSpritePaletteUsingHeap(&gBallSpritePalettes[ballId]);
    }
    switch (ballId)
    {
    case BALL_DIVE:
    case BALL_LUXURY:
    case BALL_PREMIER:
    case BALL_ULTRA:
    case BALL_TIMER:
    case BALL_NEST:
    case BALL_NET:
    case BALL_REPEAT:
        break;
    default:
        var = GetSpriteTileStartByTag(gBallSpriteSheets[ballId].tag);
        LZDecompressVram(gOpenPokeballGfx, (void *)(VRAM + 0x10100 + var * 32));
        break;
    }


Just add your own entries by adding
case BALL_OUR_OWN_BALL:
Save and compile and you are good to go.
Age 34
Male
Seen 3 Weeks Ago
Posted December 9th, 2022
134 posts
9.5 Years

Remove badge boosts



Modern Pokemon games have done away with badge boosts and instead have the minimum level for obedience increase with every badge you acquire.

Ruby



First, open calculate_base_damage.c and edit the function CalculateBaseDamage by removing the BADGE_BOOST macro and the four lines where it's used.

#define BADGE_BOOST(badge, stat, bank) ({ \
if (!(gBattleTypeFlags & (BATTLE_TYPE_LINK | BATTLE_TYPE_BATTLE_TOWER | BATTLE_TYPE_EREADER_TRAINER))) \
{ \
    if ((gBattleTypeFlags & BATTLE_TYPE_TRAINER) \
    && gTrainerBattleOpponent != SECRET_BASE_OPPONENT \
    && FlagGet(FLAG_BADGE0##badge##_GET) \
    && GetBattlerSide(bank) == B_SIDE_PLAYER) \
        (stat) = (110 * (stat)) / 100; \
} \
})
    BADGE_BOOST(1, attack, bankAtk);
    BADGE_BOOST(5, defense, bankDef);
    BADGE_BOOST(7, spAttack, bankAtk);
    BADGE_BOOST(7, spDefense, bankDef);
Next, open battle_2.c and edit the function GetWhoStrikesFirst by removing these two blocks of code.

    // Only give badge speed boost to the player's mon.
    if (!(gBattleTypeFlags & BATTLE_TYPE_LINK) && FlagGet(FLAG_BADGE03_GET) && GetBattlerSide(bank1) == 0)
        bank1AdjustedSpeed = (bank1AdjustedSpeed * 110) / 100;
    // Only give badge speed boost to the player's mon.
    if (!(gBattleTypeFlags & BATTLE_TYPE_LINK) && FlagGet(FLAG_BADGE03_GET) && GetBattlerSide(bank2) == 0)
    {
        bank2AdjustedSpeed = (bank2AdjustedSpeed * 110) / 100;
    }
Finally, open battle_util.c and edit the function IsMonDisobedient. You should be able to find this section of code.

	if (FlagGet(FLAG_BADGE02_GET))
	    obedienceLevel = 30;
	if (FlagGet(FLAG_BADGE04_GET))
	    obedienceLevel = 50;
	if (FlagGet(FLAG_BADGE06_GET))
            obedienceLevel = 70;
How badge boosts correspond to increases in the minimum obedience level is up to you. As an example, here's how obedience works in Omega Ruby/Alpha Sapphire, Gen 7 and the Let's Go games. (For X/Y-style obedience, increase all these values by 10.)

	if (FlagGet(FLAG_BADGE01_GET))
	    obedienceLevel = 20;
	if (FlagGet(FLAG_BADGE02_GET))
	    obedienceLevel = 30;
	if (FlagGet(FLAG_BADGE03_GET))
	    obedienceLevel = 40;
	if (FlagGet(FLAG_BADGE04_GET))
	    obedienceLevel = 50;
	if (FlagGet(FLAG_BADGE05_GET))
	    obedienceLevel = 60;
	if (FlagGet(FLAG_BADGE06_GET))
            obedienceLevel = 70;
	if (FlagGet(FLAG_BADGE07_GET))
	    obedienceLevel = 80;

Emerald



First, open pokemon.c and edit the function CalculateBaseDamage by removing the ShouldGetBadgeBoost function and the eight lines of code that use it.

static bool8 ShouldGetStatBadgeBoost(u16 flagId, u8 battlerId);
    if (ShouldGetStatBadgeBoost(FLAG_BADGE01_GET, battlerIdAtk))
        attack = (110 * attack) / 100;
    if (ShouldGetStatBadgeBoost(FLAG_BADGE05_GET, battlerIdDef))
        defense = (110 * defense) / 100;
    if (ShouldGetStatBadgeBoost(FLAG_BADGE07_GET, battlerIdAtk))
        spAttack = (110 * spAttack) / 100;
    if (ShouldGetStatBadgeBoost(FLAG_BADGE07_GET, battlerIdDef))
        spDefense = (110 * spDefense) / 100;
static bool8 ShouldGetStatBadgeBoost(u16 badgeFlag, u8 battlerId)
{
    if (gBattleTypeFlags & (BATTLE_TYPE_LINK | BATTLE_TYPE_EREADER_TRAINER | BATTLE_TYPE_x2000000 | BATTLE_TYPE_FRONTIER))
        return FALSE;
    else if (GetBattlerSide(battlerId) != B_SIDE_PLAYER)
        return FALSE;
    else if (gBattleTypeFlags & BATTLE_TYPE_TRAINER && gTrainerBattleOpponent_A == TRAINER_SECRET_BASE)
        return FALSE;
    else if (FlagGet(badgeFlag))
        return TRUE;
    else
        return FALSE;
}
Next, open battle_main.c and edit the function GetWhoStrikesFirst. There are two blocks of seven lines of code with the comment "badge boost"; remove them.

    // badge boost
    if (!(gBattleTypeFlags & (BATTLE_TYPE_LINK | BATTLE_TYPE_x2000000 | BATTLE_TYPE_FRONTIER))
        && FlagGet(FLAG_BADGE03_GET)
        && GetBattlerSide(battler1) == B_SIDE_PLAYER)
    {
        speedBattler1 = (speedBattler1 * 110) / 100;
    }
    // badge boost
    if (!(gBattleTypeFlags & (BATTLE_TYPE_LINK | BATTLE_TYPE_x2000000 | BATTLE_TYPE_FRONTIER))
        && FlagGet(FLAG_BADGE03_GET)
        && GetBattlerSide(battler2) == B_SIDE_PLAYER)
    {
        speedBattler2 = (speedBattler2 * 110) / 100;
    }
Finally, open battle_util.c and edit the function IsMonDisobedient. You should be able to find this section of code.

        if (FlagGet(FLAG_BADGE02_GET))
            obedienceLevel = 30;
        if (FlagGet(FLAG_BADGE04_GET))
            obedienceLevel = 50;
        if (FlagGet(FLAG_BADGE06_GET))
            obedienceLevel = 70;
How badge boosts correspond to increases in the minimum obedience level is up to you. As an example, here's how obedience works in Omega Ruby/Alpha Sapphire, Gen 7 and the Let's Go games. (For X/Y-style obedience, increase all these values by 10.)

        if (FlagGet(FLAG_BADGE01_GET))
            obedienceLevel = 20;
        if (FlagGet(FLAG_BADGE02_GET))
            obedienceLevel = 30;
        if (FlagGet(FLAG_BADGE03_GET))
            obedienceLevel = 40;
        if (FlagGet(FLAG_BADGE04_GET))
            obedienceLevel = 50;
        if (FlagGet(FLAG_BADGE05_GET))
            obedienceLevel = 60;
        if (FlagGet(FLAG_BADGE06_GET))
            obedienceLevel = 70;
        if (FlagGet(FLAG_BADGE07_GET))
            obedienceLevel = 80;
The rival in Red and Blue is called Green. Gary is some character from a lame cartoon.
Age 34
Male
Seen 3 Weeks Ago
Posted December 9th, 2022
134 posts
9.5 Years

Change the limbo slots' hardcoded Unown cry



First, open the file pokemon_3.c (Ruby)/pokemon.c (Emerald) and find the function SpeciesToCryId. Remove the two if statements and alter the final line such that the function looks like this.

u16 SpeciesToCryId(u16 species)
{
    return gSpeciesIdToCryId[species];
}
Now extend the list gSpeciesIdToCryId in cry_ids.h to include BULBASAUR through OLD_UNOWN_Z. To replicate the vanilla experience the first 251 entries (Bulbasaur through Celebi) should be the National Pokédex number of that Pokémon minus one; after that are 26 copies of the value 200 for the limbo slots.
The rival in Red and Blue is called Green. Gary is some character from a lame cartoon.

DizzyEgg

Age 25
Male
Poland
Seen March 23rd, 2023
Posted April 23rd, 2020
794 posts
9.3 Years
Nature-affected stats colouring

I used the color red with light and dark shade to signify stat affected negatively and positively respectively. If you wish to use different colors, just modify the sTextNatureDown and sTextNatureUp strings, more specifically the byte value after {COLOR} special character.
src/pokemon_summary_screen.c
Changes include functions in range BufferLeftColumnStats - BufferRightColumnStats.
Spoiler:

static void BufferStat(u8 *dst, s8 natureMod, u32 stat, u32 strId, u32 n)
{
    static const u8 sTextNatureDown[] = _("{COLOR}{09}");
    static const u8 sTextNatureUp[] = _("{COLOR}{05}");
    static const u8 sTextNatureNeutral[] = _("{COLOR}{01}");
    u8 *txtPtr;

    if (natureMod == 0)
        txtPtr = StringCopy(dst, sTextNatureNeutral);
    else if (natureMod > 0)
        txtPtr = StringCopy(dst, sTextNatureUp);
    else
        txtPtr = StringCopy(dst, sTextNatureDown);

    ConvertIntToDecimalStringN(txtPtr, stat, STR_CONV_MODE_RIGHT_ALIGN, n);
    DynamicPlaceholderTextUtil_SetPlaceholderPtr(strId, dst);
}

static void BufferLeftColumnStats(void)
{
    u8 *currentHPString = Alloc(20);
    u8 *maxHPString = Alloc(20);
    u8 *attackString = Alloc(20);
    u8 *defenseString = Alloc(20);
    const s8 *natureMod = gNatureStatTable[sMonSummaryScreen->summary.nature];

    DynamicPlaceholderTextUtil_Reset();
    BufferStat(currentHPString, 0, sMonSummaryScreen->summary.currentHP, 0, 3);
    BufferStat(maxHPString, 0, sMonSummaryScreen->summary.maxHP, 1, 3);
    BufferStat(attackString, natureMod[STAT_ATK - 1], sMonSummaryScreen->summary.atk, 2, 7);
    BufferStat(defenseString, natureMod[STAT_DEF - 1], sMonSummaryScreen->summary.def, 3, 7);
    DynamicPlaceholderTextUtil_ExpandPlaceholders(gStringVar4, sStatsLeftColumnLayout);

    Free(currentHPString);
    Free(maxHPString);
    Free(attackString);
    Free(defenseString);
}

static void PrintLeftColumnStats(void)
{
    SummaryScreen_PrintTextOnWindow(AddWindowFromTemplateList(sPageSkillsTemplate, PSS_DATA_WINDOW_SKILLS_STATS_LEFT), gStringVar4, 4, 1, 0, 0);
}

static void BufferRightColumnStats(void)
{
    const s8 *natureMod = gNatureStatTable[sMonSummaryScreen->summary.nature];

    DynamicPlaceholderTextUtil_Reset();
    BufferStat(gStringVar1, natureMod[STAT_SPATK - 1], sMonSummaryScreen->summary.spatk, 0, 3);
    BufferStat(gStringVar2, natureMod[STAT_SPDEF - 1], sMonSummaryScreen->summary.spdef, 1, 3);
    BufferStat(gStringVar3, natureMod[STAT_SPEED - 1], sMonSummaryScreen->summary.speed, 2, 3);
    DynamicPlaceholderTextUtil_ExpandPlaceholders(gStringVar4, sStatsRightColumnLayout);
}

Screenshot:
Spoiler:



I also have the code in my github branch called nature_color, so you can pull directly from it.
Support Pokeemerald!

Pokeemerald starter pack:
Emerald Expansion
Male
Yorkshire, UK
Seen 2 Weeks Ago
Posted April 20th, 2021
106 posts
10.5 Years
Removing Badge Checks for HMs

As it says, it's just a quick way to disable badge checks so HM moves can be used without gym badges. For now, I've only commented out the code in case I need to undo it later, however, you could simply delete the commented code if you wished.

In:
src/field_control_Avatar.c
Spoiler:
For Surf, Replace:
if (FlagGet(FLAG_BADGE05_GET) == TRUE && PartyHasMonWithSurf() == TRUE && IsPlayerFacingSurfableFishableWater() == TRUE)
With:
if (/*FlagGet(FLAG_BADGE05_GET) == TRUE && */PartyHasMonWithSurf() == TRUE && IsPlayerFacingSurfableFishableWater() == TRUE)
For Waterfall, Replace:
if (FlagGet(FLAG_BADGE08_GET) == TRUE && IsPlayerSurfingNorth() == TRUE)
With:
if (/*FlagGet(FLAG_BADGE08_GET) == TRUE && */IsPlayerSurfingNorth() == TRUE)
For Dive, Replace:
if (FlagGet(FLAG_BADGE07_GET) && TrySetDiveWarp() == 2)
With:
if (/*FlagGet(FLAG_BADGE07_GET) && */TrySetDiveWarp() == 2)
And
if (FlagGet(FLAG_BADGE07_GET) && gMapHeader.mapType == MAP_TYPE_UNDERWATER && TrySetDiveWarp() == 1)
With:
if (/*FlagGet(FLAG_BADGE07_GET) && */gMapHeader.mapType == MAP_TYPE_UNDERWATER && TrySetDiveWarp() == 1)


In:
data\scripts\field_move_scripts.inc
Spoiler:
Add "//" at the start of these lines
For Cut:
goto_if_unset FLAG_BADGE01_GET, EventScript_CheckTreeCantCut
Becomes:
//goto_if_unset FLAG_BADGE01_GET, EventScript_CheckTreeCantCut
For Rock Smash:
goto_if_unset FLAG_BADGE03_GET, EventScript_CantSmashRock
Becomes:
//goto_if_unset FLAG_BADGE03_GET, EventScript_CantSmashRock
For Strength:
goto_if_unset FLAG_BADGE04_GET, EventScript_CantStrength
Becomes:
//goto_if_unset FLAG_BADGE04_GET, EventScript_CantStrength


And finally, in:
src\party_menu.c
Spoiler:
To disable badge check on Field Moves when used from the Party Menu, simply add "/*" before "if" and "*/" after "else". So this:
        // All field moves before WATERFALL are HMs.
        if (fieldMove <= FIELD_MOVE_WATERFALL && FlagGet(FLAG_BADGE01_GET + fieldMove) != TRUE)
        {
            sub_81B1B5C(gText_CantUseUntilNewBadge, 1);
            gTasks[taskId].func = sub_81B1C1C;
        }
        else if (sFieldMoveCursorCallbacks[fieldMove].fieldMoveFunc() == TRUE)
Should look like:
        // All field moves before WATERFALL are HMs.
        /*if (fieldMove <= FIELD_MOVE_WATERFALL && FlagGet(FLAG_BADGE01_GET + fieldMove) != TRUE)
        {
            sub_81B1B5C(gText_CantUseUntilNewBadge, 1);
            gTasks[taskId].func = sub_81B1C1C;
        }
        else */if (sFieldMoveCursorCallbacks[fieldMove].fieldMoveFunc() == TRUE)

Buffel Saft

Male
Seen 3 Hours Ago
Posted 3 Weeks Ago
1,550 posts
9.7 Years
Trainer Class-Based Poké Balls (Emerald)
Trainers in the gen VII games send out their Pokemon in different balls depending what class they are, so I've implemented that in Emerald.

First, go to include\battle_main.h and add a new struct like so:
struct TrainerBall
{
    u8 classId;
    u8 Ball; // make this a u16 if needed
};
Then add a table of these to src\battle_main.c:
Spoiler:
const struct TrainerBall gTrainerBallTable[] = {
    {TRAINER_CLASS_TEAM_AQUA, ITEM_NET_BALL},
    {TRAINER_CLASS_AQUA_ADMIN, ITEM_NET_BALL},
    {TRAINER_CLASS_AQUA_LEADER, ITEM_MASTER_BALL},
    {TRAINER_CLASS_AROMA_LADY, ITEM_FRIEND_BALL},
    {TRAINER_CLASS_RUIN_MANIAC, ITEM_DUSK_BALL},
    {TRAINER_CLASS_INTERVIEWER, ITEM_REPEAT_BALL},
    {TRAINER_CLASS_TUBER_F, ITEM_DIVE_BALL},
    {TRAINER_CLASS_TUBER_M, ITEM_DIVE_BALL},
    {TRAINER_CLASS_SIS_AND_BRO, ITEM_POKE_BALL},
    {TRAINER_CLASS_COOLTRAINER, ITEM_ULTRA_BALL},
    {TRAINER_CLASS_HEX_MANIAC, ITEM_DUSK_BALL},
    {TRAINER_CLASS_LADY, ITEM_LUXURY_BALL},
    {TRAINER_CLASS_BEAUTY, ITEM_LOVE_BALL},
    {TRAINER_CLASS_RICH_BOY, ITEM_LUXURY_BALL},
    {TRAINER_CLASS_POKEMANIAC, ITEM_MOON_BALL},
    {TRAINER_CLASS_SWIMMER_M, ITEM_DIVE_BALL},
    {TRAINER_CLASS_BLACK_BELT, ITEM_HEAVY_BALL},
    {TRAINER_CLASS_GUITARIST, ITEM_FAST_BALL},
    {TRAINER_CLASS_KINDLER, ITEM_POKE_BALL},
    {TRAINER_CLASS_CAMPER, ITEM_NEST_BALL},
    {TRAINER_CLASS_OLD_COUPLE, ITEM_POKE_BALL},
    {TRAINER_CLASS_BUG_MANIAC, ITEM_NET_BALL},
    {TRAINER_CLASS_PSYCHIC, ITEM_DREAM_BALL},
    {TRAINER_CLASS_GENTLEMAN, ITEM_LUXURY_BALL},
    {TRAINER_CLASS_ELITE_FOUR, ITEM_ULTRA_BALL},
    {TRAINER_CLASS_LEADER, ITEM_ULTRA_BALL},
    {TRAINER_CLASS_SCHOOL_KID, ITEM_POKE_BALL},
    {TRAINER_CLASS_SR_AND_JR, ITEM_POKE_BALL},
    {TRAINER_CLASS_POKEFAN, ITEM_POKE_BALL},
    {TRAINER_CLASS_EXPERT, ITEM_ULTRA_BALL},
    {TRAINER_CLASS_YOUNGSTER, ITEM_POKE_BALL},
    {TRAINER_CLASS_CHAMPION, ITEM_CHERISH_BALL},
    {TRAINER_CLASS_FISHERMAN, ITEM_LURE_BALL},
    {TRAINER_CLASS_TRIATHLETE, ITEM_FAST_BALL},
    {TRAINER_CLASS_DRAGON_TAMER, ITEM_ULTRA_BALL},
    {TRAINER_CLASS_BIRD_KEEPER, ITEM_QUICK_BALL},
    {TRAINER_CLASS_NINJA_BOY, ITEM_QUICK_BALL},
    {TRAINER_CLASS_BATTLE_GIRL, ITEM_HEAVY_BALL},
    {TRAINER_CLASS_PARASOL_LADY, ITEM_POKE_BALL},
    {TRAINER_CLASS_SWIMMER_F, ITEM_DIVE_BALL},
    {TRAINER_CLASS_PICNICKER, ITEM_FRIEND_BALL},
    {TRAINER_CLASS_TWINS, ITEM_POKE_BALL},
    {TRAINER_CLASS_SAILOR, ITEM_DIVE_BALL},
    {TRAINER_CLASS_COLLECTOR, ITEM_REPEAT_BALL},
    {TRAINER_CLASS_PKMN_TRAINER_3, ITEM_PREMIER_BALL},
    {TRAINER_CLASS_PKMN_BREEDER, ITEM_TIMER_BALL},
    {TRAINER_CLASS_PKMN_RANGER, ITEM_SAFARI_BALL},
    {TRAINER_CLASS_TEAM_MAGMA, ITEM_NEST_BALL},
    {TRAINER_CLASS_MAGMA_ADMIN, ITEM_NEST_BALL},
    {TRAINER_CLASS_MAGMA_LEADER, ITEM_MASTER_BALL},
    {TRAINER_CLASS_LASS, ITEM_POKE_BALL},
    {TRAINER_CLASS_BUG_CATCHER, ITEM_NET_BALL},
    {TRAINER_CLASS_HIKER, ITEM_HEAVY_BALL},
    {TRAINER_CLASS_YOUNG_COUPLE, ITEM_LOVE_BALL},
    {TRAINER_CLASS_WINSTRATE, ITEM_GREAT_BALL},
    {TRAINER_CLASS_PKMN_TRAINER_2, ITEM_HEAVY_BALL},
    {0xFF, ITEM_POKE_BALL},
};

Finally, add the code in bold to the end of the first for loop in the function CreateNPCTrainerParty, which is also in src\battle_main.c, like so:
Spoiler:
                for (j = 0; j < MAX_MON_MOVES; j++)
                {
                    SetMonData(&party[i], MON_DATA_MOVE1 + j, &partyData[i].moves[j]);
                    SetMonData(&party[i], MON_DATA_PP1 + j, &gBattleMoves[partyData[i].moves[j]].pp);
                }
                break;
            }
            }
// New code starts here
            for (j = 0; gTrainerBallTable[j].classId != 0xFF; j++)
            {
                if (gTrainerBallTable[j].classId == gTrainers[trainerNum].trainerClass)
                    break;
            }
            SetMonData(&party[i], MON_DATA_POKEBALL, &gTrainerBallTable[j].Ball);
        }

Buffel Saft

Male
Seen 3 Hours Ago
Posted 3 Weeks Ago
1,550 posts
9.7 Years
BUGGED: Evolution Moves (Emerald)

NOTE: I've just found a nasty bug with this: if a Pokemon tries to learn a move it already knows while evolving, the game will freeze. I haven't been able to find the cause, so I'd recommend using the implementation posted by Lunos instead.

In the gen VII games many Pokemon have level up moves that they learn upon evolving, regardless of what level they are. This is a fantastic addition because it means Pokemon like Feebas don't stay useless if they evolve a level too late to learn their first damaging move.

To implement this in Emerald, first open include/pokemon.h and add an extra input argument to the function "MonTryLearningNewMove":
u16 MonTryLearningNewMove(struct Pokemon *mon, bool8 firstMove, bool8 isEvolving);
Next, modify all code that calls this function to include a third input argument. There are two instances in src/evolution_scene.c where this should be set to "1", like so:
var = MonTryLearningNewMove(mon, gTasks[taskID].tLearnsFirstMove, 1);
All other instances should be set to "0". These can be found in src/party_menu.c, src/battle_script_commands.c, and src/daycare.c.

Following that, open pokemon.c and navigate to the function "MonTryLearningNewMove". Replace it with the following:
Spoiler:
u16 MonTryLearningNewMove(struct Pokemon *mon, bool8 firstMove, bool8 isEvolving)
{
    u32 retVal = 0;
    u16 species = GetMonData(mon, MON_DATA_SPECIES, NULL);
    u8 level = GetMonData(mon, MON_DATA_LEVEL, NULL);

    // since you can learn more than one move per level
    // the game needs to know whether you decided to
    // learn it or keep the old set to avoid asking
    // you to learn the same move over and over again
    if (firstMove)
    {
        sLearningMoveTableID = 0;
    }
    // Added evolution moves; Pokemon will learn moves listed at level zero upon evolution
    if(isEvolving && (gLevelUpLearnsets[species][sLearningMoveTableID].level == 0))
    {
        gMoveToLearn = gLevelUpLearnsets[species][sLearningMoveTableID].move;
        retVal = GiveMoveToMon(mon, gMoveToLearn);
        sLearningMoveTableID++;
        return retVal;        
    }
    if(isEvolving && (gLevelUpLearnsets[species][sLearningMoveTableID].level > 0))
    {
        while (gLevelUpLearnsets[species][sLearningMoveTableID].level != level)
         {
            sLearningMoveTableID++;
            if (gLevelUpLearnsets[species][sLearningMoveTableID].move == LEVEL_UP_END)
                return 0;
         }
    }

    if (firstMove)
    {
        while (gLevelUpLearnsets[species][sLearningMoveTableID].level != level)
        {
            sLearningMoveTableID++;
            if (gLevelUpLearnsets[species][sLearningMoveTableID].move == LEVEL_UP_END)
                return 0;
        }
    }
    if (gLevelUpLearnsets[species][sLearningMoveTableID].level == level)
    {
        gMoveToLearn = gLevelUpLearnsets[species][sLearningMoveTableID].move;
        sLearningMoveTableID++;
        retVal = GiveMoveToMon(mon, gMoveToLearn);
    }
    return retVal;
}

Finally, add some evolution moves to src\data\pokemon\level_up_learnsets.h. These must be the first entries in each Pokemon's list, and must be learned at level zero e.g.
Spoiler:
static const struct LevelUpMove sMarshtompLevelUpLearnset[] = {
    LEVEL_UP_MOVE( 0, MOVE_MUD_SHOT),
    LEVEL_UP_MOVE( 1, MOVE_MUD_SHOT),
    LEVEL_UP_MOVE( 1, MOVE_TACKLE),
    LEVEL_UP_MOVE( 1, MOVE_GROWL),
    LEVEL_UP_MOVE( 1, MOVE_WATER_GUN),
    LEVEL_UP_MOVE( 1, MOVE_MUD_SLAP),
    LEVEL_UP_MOVE( 4, MOVE_WATER_GUN),
    LEVEL_UP_MOVE( 9, MOVE_MUD_SLAP),
    LEVEL_UP_MOVE(12, MOVE_FORESIGHT),
    LEVEL_UP_MOVE(18, MOVE_BIDE),
    LEVEL_UP_MOVE(22, MOVE_MUD_BOMB),
    LEVEL_UP_MOVE(28, MOVE_ROCK_SLIDE),
    LEVEL_UP_MOVE(32, MOVE_PROTECT),
    LEVEL_UP_MOVE(38, MOVE_MUDDY_WATER),
    LEVEL_UP_MOVE(42, MOVE_TAKE_DOWN),
    LEVEL_UP_MOVE(48, MOVE_EARTHQUAKE),
    LEVEL_UP_MOVE(52, MOVE_ENDEAVOR),
    LEVEL_UP_END
};
Seen March 26th, 2023
Posted September 3rd, 2021
50 posts
5.9 Years
Trainer Pokemon Individual Poké Balls (Emerald)

After being inspired by Buffel Saft - Trainer Class-Based Poké Balls (Emerald) I wanted to do the same but with a different aproach. Where every Trainers Poké Ball can optionally be changed in src/data/trainer_parties.h.

const struct TrainerMonNoItemDefaultMoves gTrainerParty_May7[] = {
    {
    .iv_ball = ((ITEM_ULTRA_BALL << 8) | 0),
    .lvl = 5,
    .species = SPECIES_DEOXYS,
    }
};
I noticed the .iv propperty is stored in an u16 and never exceeds 255 wich leaves the 8 upper bits unused (for as far as I know). So I used the 8 upper bits for a ball.


In src/data/trainer_parties.h.
Spoiler:

Change all .iv to .iv_ball
From:
const struct TrainerMonNoItemDefaultMoves gTrainerParty_Sawyer1[] = {
    {
    .iv = 0,
    .lvl = 21,
    .species = SPECIES_GEODUDE,
    }
};

to

const struct TrainerMonNoItemDefaultMoves gTrainerParty_Sawyer1[] = {
    {
    .iv_ball = 0,
    .lvl = 21,
    .species = SPECIES_GEODUDE,
    }
};
You surely want to use some kind of find and replace for this.


In include/battle.h Do the same
From:
struct TrainerMonNoItemDefaultMoves
{
    u16 iv;
    u8 lvl;
    u16 species;
};

to

struct TrainerMonNoItemDefaultMoves
{
    u16 iv_ball;
    u8 lvl;
    u16 species;
};
Do the same for [TrainerMonItemDefaultMoves, TrainerMonNoItemCustomMoves, TrainerMonItemCustomMoves]


Than still in include/battle.h, add these 2 lines at the top.
Spoiler:

#define BATTLE_BUFFER_LINK_SIZE 0x1000

//new lines
#define GET_TRAINERMON_IV(trainermon)       (trainermon &  0xFF)
#define GET_TRAINERMON_BALL(trainermon)     (trainermon >> 8)

struct TrainerMonNoItemDefaultMoves
{


Than in src/battle_main.c go to CreateNPCTrainerParty
Spoiler:

Change every occurence of
 fixedIV = partyData[i].iv * 31 / 255;
to
 fixedIV = GET_TRAINERMON_IV(partyData[i].iv_ball) * 31 / 255;
And add before every break in the switch statemant.

 if (GET_TRAINERMON_BALL(partyData[i].iv_ball) != 0) {
                                     u8 ball = GET_TRAINERMON_BALL(partyData[i].iv_ball);
                                       SetMonData(&party[i], MON_DATA_POKEBALL, &ball);
  }

Full version:
Spoiler:

Change
            switch (gTrainers[trainerNum].partyFlags)
            {
            case 0:
            {
                const struct TrainerMonNoItemDefaultMoves *partyData = gTrainers[trainerNum].party.NoItemDefaultMoves;

                for (j = 0; gSpeciesNames[partyData[i].species][j] != EOS; j++)
                    nameHash += gSpeciesNames[partyData[i].species][j];

                personalityValue += nameHash << 8;
                fixedIV = partyData[i].iv * 31 / 255;
                CreateMon(&party[i], partyData[i].species, partyData[i].lvl, fixedIV, TRUE, personalityValue, OT_ID_RANDOM_NO_SHINY, 0);
                break;
            }
            case F_TRAINER_PARTY_CUSTOM_MOVESET:
            {
                const struct TrainerMonNoItemCustomMoves *partyData = gTrainers[trainerNum].party.NoItemCustomMoves;

                for (j = 0; gSpeciesNames[partyData[i].species][j] != EOS; j++)
                    nameHash += gSpeciesNames[partyData[i].species][j];

                personalityValue += nameHash << 8;
                fixedIV = partyData[i].iv * 31 / 255;
                CreateMon(&party[i], partyData[i].species, partyData[i].lvl, fixedIV, TRUE, personalityValue, 2, 0);

                for (j = 0; j < MAX_MON_MOVES; j++)
                {
                    SetMonData(&party[i], MON_DATA_MOVE1 + j, &partyData[i].moves[j]);
                    SetMonData(&party[i], MON_DATA_PP1 + j, &gBattleMoves[partyData[i].moves[j]].pp);
                }
                break;
            }
            case F_TRAINER_PARTY_HELD_ITEM:
            {
                const struct TrainerMonItemDefaultMoves *partyData = gTrainers[trainerNum].party.ItemDefaultMoves;

                for (j = 0; gSpeciesNames[partyData[i].species][j] != EOS; j++)
                    nameHash += gSpeciesNames[partyData[i].species][j];

                personalityValue += nameHash << 8;
                fixedIV = partyData[i].iv * 31 / 255;
                CreateMon(&party[i], partyData[i].species, partyData[i].lvl, fixedIV, TRUE, personalityValue, 2, 0);

                SetMonData(&party[i], MON_DATA_HELD_ITEM, &partyData[i].heldItem);
                break;
            }
            case F_TRAINER_PARTY_CUSTOM_MOVESET | F_TRAINER_PARTY_HELD_ITEM:
            {
                const struct TrainerMonItemCustomMoves *partyData = gTrainers[trainerNum].party.ItemCustomMoves;

                for (j = 0; gSpeciesNames[partyData[i].species][j] != EOS; j++)
                    nameHash += gSpeciesNames[partyData[i].species][j];

                personalityValue += nameHash << 8;
                fixedIV = partyData[i].iv * 31 / 255;
                CreateMon(&party[i], partyData[i].species, partyData[i].lvl, fixedIV, TRUE, personalityValue, 2, 0);

                SetMonData(&party[i], MON_DATA_HELD_ITEM, &partyData[i].heldItem);

                for (j = 0; j < MAX_MON_MOVES; j++)
                {
                    SetMonData(&party[i], MON_DATA_MOVE1 + j, &partyData[i].moves[j]);
                    SetMonData(&party[i], MON_DATA_PP1 + j, &gBattleMoves[partyData[i].moves[j]].pp);
                }
                break;
            }
            }
to

            switch (gTrainers[trainerNum].partyFlags)
            {
            case 0:
            {
                const struct TrainerMonNoItemDefaultMoves *partyData = gTrainers[trainerNum].party.NoItemDefaultMoves;

                for (j = 0; gSpeciesNames[partyData[i].species][j] != EOS; j++)
                    nameHash += gSpeciesNames[partyData[i].species][j];

                personalityValue += nameHash << 8;
                fixedIV = GET_TRAINERMON_IV(partyData[i].iv_ball) * 31 / 255;
                CreateMon(&party[i], partyData[i].species, partyData[i].lvl, fixedIV, TRUE, personalityValue, OT_ID_RANDOM_NO_SHINY, 0);
				if (GET_TRAINERMON_BALL(partyData[i].iv_ball) != 0) {
					u8 ball = GET_TRAINERMON_BALL(partyData[i].iv_ball);
					SetMonData(&party[i], MON_DATA_POKEBALL, &ball);
				}
                break;
            }
            case F_TRAINER_PARTY_CUSTOM_MOVESET:
            {
                const struct TrainerMonNoItemCustomMoves *partyData = gTrainers[trainerNum].party.NoItemCustomMoves;

                for (j = 0; gSpeciesNames[partyData[i].species][j] != EOS; j++)
                    nameHash += gSpeciesNames[partyData[i].species][j];

                personalityValue += nameHash << 8;
                fixedIV = GET_TRAINERMON_IV(partyData[i].iv_ball) * 31 / 255;
                CreateMon(&party[i], partyData[i].species, partyData[i].lvl, fixedIV, TRUE, personalityValue, 2, 0);

                for (j = 0; j < MAX_MON_MOVES; j++)
                {
                    SetMonData(&party[i], MON_DATA_MOVE1 + j, &partyData[i].moves[j]);
                    SetMonData(&party[i], MON_DATA_PP1 + j, &gBattleMoves[partyData[i].moves[j]].pp);
                }
				if (GET_TRAINERMON_BALL(partyData[i].iv_ball) != 0) {
					u8 ball = GET_TRAINERMON_BALL(partyData[i].iv_ball);
					SetMonData(&party[i], MON_DATA_POKEBALL, &ball);
				}
                break;
            }
            case F_TRAINER_PARTY_HELD_ITEM:
            {
                const struct TrainerMonItemDefaultMoves *partyData = gTrainers[trainerNum].party.ItemDefaultMoves;

                for (j = 0; gSpeciesNames[partyData[i].species][j] != EOS; j++)
                    nameHash += gSpeciesNames[partyData[i].species][j];

                personalityValue += nameHash << 8;
                fixedIV = GET_TRAINERMON_IV(partyData[i].iv_ball) * 31 / 255;
                CreateMon(&party[i], partyData[i].species, partyData[i].lvl, fixedIV, TRUE, personalityValue, 2, 0);

                SetMonData(&party[i], MON_DATA_HELD_ITEM, &partyData[i].heldItem);
				if (GET_TRAINERMON_BALL(partyData[i].iv_ball) != 0) {
					u8 ball = GET_TRAINERMON_BALL(partyData[i].iv_ball);
					SetMonData(&party[i], MON_DATA_POKEBALL, &ball);
				}
                break;
            }
            case F_TRAINER_PARTY_CUSTOM_MOVESET | F_TRAINER_PARTY_HELD_ITEM:
            {
                const struct TrainerMonItemCustomMoves *partyData = gTrainers[trainerNum].party.ItemCustomMoves;

                for (j = 0; gSpeciesNames[partyData[i].species][j] != EOS; j++)
                    nameHash += gSpeciesNames[partyData[i].species][j];

                personalityValue += nameHash << 8;
                fixedIV = GET_TRAINERMON_IV(partyData[i].iv_ball) * 31 / 255;
                CreateMon(&party[i], partyData[i].species, partyData[i].lvl, fixedIV, TRUE, personalityValue, 2, 0);

                SetMonData(&party[i], MON_DATA_HELD_ITEM, &partyData[i].heldItem);

                for (j = 0; j < MAX_MON_MOVES; j++)
                {
                    SetMonData(&party[i], MON_DATA_MOVE1 + j, &partyData[i].moves[j]);
                    SetMonData(&party[i], MON_DATA_PP1 + j, &gBattleMoves[partyData[i].moves[j]].pp);
                }
				if (GET_TRAINERMON_BALL(partyData[i].iv_ball) != 0) {
					u8 ball = GET_TRAINERMON_BALL(partyData[i].iv_ball);
					SetMonData(&party[i], MON_DATA_POKEBALL, &ball);
				}
                break;
            }
            }


Result

Lunos

Random Uruguayan User

Male
Montevideo (Uruguay)
Seen 7 Hours Ago
Posted 1 Day Ago
3,006 posts
14.7 Years
Gen. 6 styled Exp. Share (Em)
Blurose implemented this mechanic from the newer pokémon games on Pokeruby sometime ago.
Today, I took some time to port his version to Pokeemerald.
To add it to your project, you just need to track my repository via
git remote
and pull the branch where the code is.

For battle_engine users:
git remote add lunos https://github.com/LOuroboros/pokeemerald
git pull lunos gen6_exp_share_BE
For vanilla Pokeemerald users:
git remote add lunos https://github.com/LOuroboros/pokeemerald
git pull lunos gen6_exp_share
Video:


EDIT (07/03/2019):
It has been brought to my attention by Discord user Lightbox that a label was changed, and since I deleted the branch where I pushed the commit I linked here originally, I decided to grab a clean copy of Pokeemerald and make a branch specifically for this feature.
I already modified the link to the commit above and it's ready to go.


EDIT (06/12/2020):
It has been brought to my attention by Jaizu that the value of gSaveBlock2Ptr->expShare wasn't being reset upon starting a New Game, which means that if you turned on the Exp. Share, saved the game, started a New Game and tried to use the Exp. Share, it would still be considered by the game as being enabled. I fixed that in the latest commit that I pushed to the branch.


EDIT (10/06/2022):
I edited the post yet again, linking an alternative branch I made for battle_engine users, mostly because handling the changes to
Cmd_getexp
can be daunting to newbies.
If anyone spots any mistakes I may have made, please let me know.

Also, this branch varies quiet a bit from the other one, as I now use an unused flag to handle toggling the system On/Off instead of adding a whole new variable to the SaveBlock1 for it pointlessly.
Feel free to change which flag is used or whatever if you want or need to.