[DECOMP] Simple Modifications Directory

Started by Avara December 30th, 2018 2:48 PM
  • 18935 views
  • 79 replies

Avara

Female
Izantine
Seen 3 Weeks Ago
Posted 4 Weeks Ago
1,206 posts
8.5 Years
Decomp Simple Modifications Directory

This thread can hopefully be used to host instructions for a variety of small changes to pokéemerald (or pokéruby) that are much too short/simple to justify having their own thread/their own page on their respective wiki. Contributions can then be listed in this post for the sake of simplicity. Please test to ensure your implementation works before submitting! If you can think of a small edit you'd like to be added to this list, go ahead and leave a comment, with a bit of luck it will help to encourage more rom hackers to switch to the decomps <3
-----------------------------------------------------------------------------------------------------------------------------------------------------
Graphics
Mechanics
Battle-related
Other Tweaks

Avara

Female
Izantine
Seen 3 Weeks Ago
Posted 4 Weeks Ago
1,206 posts
8.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

Female
Izantine
Seen 3 Weeks Ago
Posted 4 Weeks Ago
1,206 posts
8.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

Female
Izantine
Seen 3 Weeks Ago
Posted 4 Weeks Ago
1,206 posts
8.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

Female
Izantine
Seen 3 Weeks Ago
Posted 4 Weeks Ago
1,206 posts
8.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

Female
Izantine
Seen 3 Weeks Ago
Posted 4 Weeks Ago
1,206 posts
8.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:

paccy

Pokeholic

Male
'Straya
Seen 13 Hours Ago
Posted June 18th, 2019
8 posts
11 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

Female
Izantine
Seen 3 Weeks Ago
Posted 4 Weeks Ago
1,206 posts
8.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 22
Male
Poland
Seen 2 Days Ago
Posted 4 Weeks Ago
795 posts
6.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.

DizzyEgg

Age 22
Male
Poland
Seen 2 Days Ago
Posted 4 Weeks Ago
795 posts
6.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.

Lunos

Random Uruguayan User

Male
Montevideo (Uruguay)
Seen 10 Minutes Ago
Posted 20 Minutes Ago
2,484 posts
11.6 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: So, if 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

(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 22
Male
Poland
Seen 2 Days Ago
Posted 4 Weeks Ago
795 posts
6.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.
Seen 19 Hours Ago
Posted 4 Weeks Ago
6 posts
1.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).
Seen 19 Hours Ago
Posted 4 Weeks Ago
6 posts
1.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
1.8 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 5 Days Ago
Posted January 26th, 2020
22 posts
2.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.



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.


To change this style from a script set a var and make a new function where you check the var value and update the setting accordingly. And then call that funciton from the script. I'll extend this tutorial to do that if requested.
Emerald master race

Delta231

A noob

Male
India
Seen February 21st, 2020
Posted November 22nd, 2019
690 posts
4 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.

ThomasWinwood

Age 31
Male
Seen 3 Hours Ago
Posted 5 Days Ago
98 posts
6.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.

ThomasWinwood

Age 31
Male
Seen 3 Hours Ago
Posted 5 Days Ago
98 posts
6.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 22
Male
Poland
Seen 2 Days Ago
Posted 4 Weeks Ago
795 posts
6.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.

wally-217

Male
Yorkshire, UK
Seen 1 Week Ago
Posted September 3rd, 2019
103 posts
7.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 1 Day Ago
Posted 1 Day Ago
742 posts
6.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 1 Day Ago
Posted 1 Day Ago
742 posts
6.7 Years
Evolution Moves (Emerald)

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 5 Days Ago
Posted January 26th, 2020
22 posts
2.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 10 Minutes Ago
Posted 20 Minutes Ago
2,484 posts
11.6 Years
Gen. 6 styled Exp. Share
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.
You can find all the relevant changes in this commit I uploaded to a branch of my Pokeemerald repository on GitHub.
Note: Red = Original Line | Green = Modified Line

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.