• Our software update is now concluded. You will need to reset your password to log in. In order to do this, you will have to click "Log in" in the top right corner and then "Forgot your password?".
  • Welcome to PokéCommunity! Register now and join one of the best fan communities on the 'net to talk Pokémon and more! We are not affiliated with The Pokémon Company or Nintendo.

[TUTORIAL] Set Trainer's Pokémon's Levels dynamically

180
Posts
6
Years
  • Age 20
  • Seen Apr 15, 2024
And after a lot of try and error, I finally managed to get this thing working.
The features that this presents are simple;
  • Trainer Pokémon's level are 2 levels less, 2 more and everything in-between if you're overleveled
  • takes the average of yout level by adding the level of all the pokemon in your party and dividing it by the amount of pokemon in your party, taking into consideration the highest level in your team, and its difference with the lowest to make less probable that people can take advantage of this to win
  • if its a decimal number, its rounded down
  • if the results gives 2 or 1, it changes the result for 3, because if not, when it makes the rest, it gives zero or minus one

how to implement it?
just go to src/battle_main.c and inside of CreateNPCTrainerParty, define this: (thanks to DdraigZek and WiserVisor for upgrading a little bit my code and adding some cool features!)
Code:
u16 dynamicLevel = 0;
	
// This is used to hold the level's of the player's strongest[1] and weakest[0] Pokemon
u8 LevelSpread[] = {0, 0};
	
// This will be used when assigning the level of the opponent's Pokemon
u16 PartyLevelAdjust;
    
    // Change stuff like this to get the levels you want
    static const u8 minDynamicLevel = 3;
    static const u8 maxDynamicLevel = 98;

    // Calculates Average of your party's levels
    for(i = 0; i < PARTY_SIZE; i++)
    {
        if(GetMonData(&gPlayerParty[i], MON_DATA_SPECIES) == SPECIES_NONE)
        {
            if(i != 0)
		dynamicLevel /= i;
            break;
        }
        dynamicLevel += GetMonData(&gPlayerParty[i], MON_DATA_LEVEL);
	if(i == 0)
	{
	    LevelSpread[0], LevelSpread[1] = GetMonData(&gPlayerParty[i], MON_DATA_LEVEL);
	}
	else
	{
	    u8 LevelCheck = GetMonData(&gPlayerParty[i], MON_DATA_LEVEL);
	    if(LevelCheck < LevelSpread[0])
	        LevelSpread[0] = LevelCheck;
	    else if(LevelCheck > LevelSpread[1])
		LevelSpread[1] = LevelCheck;
	}
    }
    if(i == PARTY_SIZE)
		dynamicLevel /= i;
/* The following is used to account for a player having one or two very weak Pokemon
	   along with some very strong Pokemon. It weights the averaged level more towards the
	   player's strongest Pokemon
	*/
	
	PartyLevelAdjust = LevelSpread[1] - LevelSpread[0];
	
	if(LevelSpread[1] - dynamicLevel < 10)
	{
		PartyLevelAdjust = 0;
	}
	else if(LevelSpread[1] - dynamicLevel < 20)
	{
		PartyLevelAdjust /= 10;
	}
	else if(LevelSpread[1] - dynamicLevel < 30)
	{
		PartyLevelAdjust /= 5;
	}
	else if(LevelSpread[1] - dynamicLevel < 40)
	{
		PartyLevelAdjust *= 3;
		PartyLevelAdjust /= 10;
	}
	else if(LevelSpread[1] - dynamicLevel < 50)
	{
		PartyLevelAdjust *= 2;
		PartyLevelAdjust /= 5;
	}
	else if(LevelSpread[1] - dynamicLevel < 60)
	{
		PartyLevelAdjust /= 2;
	}
	else if(LevelSpread[1] - dynamicLevel < 70)
	{
		PartyLevelAdjust *= 3;
		PartyLevelAdjust /= 5;
	}
	else if(LevelSpread[1] - dynamicLevel < 80)
	{
		PartyLevelAdjust *= 7;
		PartyLevelAdjust /= 10;
	}
	else if(LevelSpread[1] - dynamicLevel < 90)
	{
		PartyLevelAdjust *= 4;
		PartyLevelAdjust /= 5;
	}
    //Handling values to be always be in the range,
    // ( minDynamiclevel-levelDifference , maxDynamiclevel+levelDifference )
    if(dynamicLevel < minDynamicLevel) dynamicLevel = minDynamicLevel;
    else if(dynamicLevel > maxDynamicLevel) dynamicLevel = maxDynamicLevel;
After that, search "for (i = 0; i < monsCount; i++)
{" and just below that, add this:
Code:
			int rand_diff = Random() % 5;
			switch(rand_diff)
			{
				case 0:
					rand_diff = 2;
					break;
				case 1:
					rand_diff = 1;
					break;
				case 2:
					rand_diff = 0;
					break;
				case 3:
					rand_diff = -1;
					break;
				case 4:
					rand_diff = -2;
			}
And, above "gBattleTypeFlags |= gTrainers[trainerNum].doubleBattle;", paste this " dynamicLevel -= rand_diff + PartyLevelAdjust;"
It should look like this: (thanks, vxo)
Code:
            }
            }
            dynamicLevel -= rand_diff + PartyLevelAdjust;
        }
        
        gBattleTypeFlags |= gTrainers[trainerNum].doubleBattle;

Now, search "CreateMon(&party, partyData.species" and replace the whole line with this:
Code:
if (partyData[i].lvl >= dynamicLevel)
                    CreateMon(&party[i], partyData[i].species, partyData[i].lvl, fixedIV, TRUE, personalityValue, OT_ID_RANDOM_NO_SHINY, 0);
else
                    CreateMon(&party[i], partyData[i].species, dynamicLevel, fixedIV, TRUE, personalityValue, OT_ID_RANDOM_NO_SHINY, 0);
This'll make the changes only appear when your team is overleveled, so you'll still have to grind a little and stuff
Evolution feature! (thanks to WiserVisor )
Spoiler:

Hope you enjoy this!
 
Last edited:
2
Posts
5
Years
  • Age 23
  • Seen Dec 18, 2020
And after a lot of try and error, I finally managed to get this thing working.
The features that this presents are simple;
  • Trainer Pokémon's level are -2, equal and +2 levels in comparison to yours depending if they have custom items, moves, both or none. -2 for none, equal for just items or just moves, and +2 if it has both of them
  • takes the average of yout level by adding the level of all the pokemon in your party and dividing it by the amount of pokemon in your party.
  • if its a decimal number, its rounded down
  • if the results gives 2 or 1, it changes the result for 3, because if not, when it makes the rest, it gives zero or minus one

how to implement it?
just go to src/battle_main.c and inside of CreateNPCTrainerParty, define this:
Code:
	u8 fixedLVL = 0;
	{
	if (GetMonData(&gPlayerParty[5], MON_DATA_SPECIES) != SPECIES_NONE)
		fixedLVL = (GetMonData(&gPlayerParty[0], MON_DATA_LEVEL) + GetMonData(&gPlayerParty[1], MON_DATA_LEVEL) + GetMonData(&gPlayerParty[2], MON_DATA_LEVEL) + GetMonData(&gPlayerParty[3], MON_DATA_LEVEL) + GetMonData(&gPlayerParty[4], MON_DATA_LEVEL) + GetMonData(&gPlayerParty[5], MON_DATA_LEVEL)) / 6;
		if (fixedLVL <= 2)
			fixedLVL = 3;
	else if ((GetMonData(&gPlayerParty[5], MON_DATA_SPECIES) == SPECIES_NONE) && (GetMonData(&gPlayerParty[4], MON_DATA_SPECIES) != SPECIES_NONE))
			fixedLVL = (GetMonData(&gPlayerParty[0], MON_DATA_LEVEL)+GetMonData(&gPlayerParty[1], MON_DATA_LEVEL)+GetMonData(&gPlayerParty[2], MON_DATA_LEVEL)+GetMonData(&gPlayerParty[3], MON_DATA_LEVEL)+GetMonData(&gPlayerParty[4], MON_DATA_LEVEL)) / 5;
			if (fixedLVL <= 2)
				fixedLVL = 3;
		else if ((GetMonData(&gPlayerParty[4], MON_DATA_SPECIES) == SPECIES_NONE) && (GetMonData(&gPlayerParty[3], MON_DATA_SPECIES) != SPECIES_NONE))
			fixedLVL = (GetMonData(&gPlayerParty[0], MON_DATA_LEVEL)+GetMonData(&gPlayerParty[1], MON_DATA_LEVEL)+GetMonData(&gPlayerParty[2], MON_DATA_LEVEL)+GetMonData(&gPlayerParty[3], MON_DATA_LEVEL)) / 4;
				if (fixedLVL <= 2)
				fixedLVL = 3;
			else if ((GetMonData(&gPlayerParty[3], MON_DATA_SPECIES) == SPECIES_NONE) && (GetMonData(&gPlayerParty[2], MON_DATA_SPECIES) != SPECIES_NONE))
				fixedLVL = (GetMonData(&gPlayerParty[0], MON_DATA_LEVEL)+GetMonData(&gPlayerParty[1], MON_DATA_LEVEL)+GetMonData(&gPlayerParty[2], MON_DATA_LEVEL)) / 3;
					if (fixedLVL <= 2)
						fixedLVL = 3;
				else if ((GetMonData(&gPlayerParty[2], MON_DATA_SPECIES) == SPECIES_NONE) && (GetMonData(&gPlayerParty[1], MON_DATA_SPECIES) != SPECIES_NONE))
					fixedLVL = (GetMonData(&gPlayerParty[0], MON_DATA_LEVEL)+GetMonData(&gPlayerParty[1], MON_DATA_LEVEL)) / 2;
						if (fixedLVL <= 2)
							fixedLVL = 3;
					else if ((GetMonData(&gPlayerParty[1], MON_DATA_SPECIES) == SPECIES_NONE) && (GetMonData(&gPlayerParty[0], MON_DATA_SPECIES) != SPECIES_NONE))
						fixedLVL = GetMonData(&gPlayerParty[0], MON_DATA_LEVEL);
						if (fixedLVL <= 2)
							fixedLVL = 3;
	}
Now, search "CreateMon(&party, partyData.species" and replace the whole line with this:
Code:
                CreateMon(&party[i], partyData[i].species, fixedLVL, fixedIV, TRUE, personalityValue, OT_ID_RANDOM_NO_SHINY, 0);
now, in the first change the " fixedLVL" for a " (fixedLVL)-2"
and in the last one, change the " fixedLVL" for a " (fixedLVL)+2"
this is just for making normal battles easy as always and rival/gym/elite 4 battles harder by making them having lower/higher levels than you.
Hope you enjoy this!


Hey Diego Mertens,
I am new to romhacking scene and I found this guide of yours. I am posting this updated version of code your code as I found that the code has no checks for a party whoose average level is 100 and it also allows someone to challenge e4 where e4's average level is like 7.

In src/battle_main.c find function CreateNPCTrainerParty, post the following code after all the variable declarations.

Code:
/*
    Dynamic values are always in range (1,100)
    They will only be used if the normal level of opponent's mons is less than the average user level.
    Opponents who have both custom moves AND held items will have dynamic range (5,100)
    otherwise the opponents will have dynamic range (1,96)
    */

    u16 dynamicLevel = 0;
    
    // Change stuff like this to get the levels you want
    static const u8 minDynamicLevel = 3;
    static const u8 maxDynamicLevel = 98;
    static const u8 levelDifference = 2;

    // Calculates Average of your party's levels
    for(i = 0; i < PARTY_SIZE; i++)
    {
        if(GetMonData(&gPlayerParty[i], MON_DATA_SPECIES) == SPECIES_NONE)
        {
            if(i != 0) dynamicLevel /= i;
            break;
        }
        dynamicLevel += GetMonData(&gPlayerParty[i], MON_DATA_LEVEL);
    }
    if(i == PARTY_SIZE) dynamicLevel /= i;

    //Handling values to be always be in the range,
    // ( minDynamiclevel-levelDifference , maxDynamiclevel+levelDifference )
    if(dynamicLevel < minDynamicLevel) dynamicLevel = minDynamicLevel;
    else if(dynamicLevel > maxDynamicLevel) dynamicLevel = maxDynamicLevel;

The above code is an elegant way to handle finding the level average and making sure the levels never get outside the range 1 to 100.

I have used u16 to store the average instead of u8 as if the party is 6 lvl 100 mons then the u8 will overflow during average calculation.

Then replace the 4 occurences of this line in switch case block,

Code:
CreateMon(&party[i], partyData[i].species, partyData[i].lvl, fixedIV, TRUE, personalityValue, OT_ID_RANDOM_NO_SHINY, 0);

with,

Code:
if (partyData[i].lvl >= dynamicLevel)
                    CreateMon(&party[i], partyData[i].species, partyData[i].lvl, fixedIV, TRUE, personalityValue, OT_ID_RANDOM_NO_SHINY, 0);
else
                    CreateMon(&party[i], partyData[i].species, dynamicLevel - levelDifference, fixedIV, TRUE, personalityValue, OT_ID_RANDOM_NO_SHINY, 0);

This code ensures that the dynamic levels are only used if the user is overleveled. Use dynamicLevel + levelDifference in last case.

The three constants minDynamicLevel, maxDynamicLevel and levelDifference are used to define the change in levels. Change them or remove "static const" and use random values to spice up stuff a bit.

--------------------------------------------------

I would also like to give more ideas to make this level generation cooler!
- Evolve opponent's mons if the level is too high or something
- Make the average weighted, so the system cannot be abuse by having a super low level pokemon in party (or somehow take into amount the max level of the party)
- Make the levels more dynamic based on the opponent's class or the area so there is still a sense of progression.

--------------------------------------------------

Offtopic but I would also like to ask two other things,
1) how to mention people on this forum.
2) Is there any constants or lists that define which pokemon evolves to which in decomp so I can use it in this code to get opponents pokemon to evolve too?

Thanks,
DdraigZek.
 
Last edited:
247
Posts
6
Years
  • Age 25
  • Seen Apr 23, 2024
2) Is there any constants or lists that define which pokemon evolves to which in decomp so I can use it in this code to get opponents pokemon to evolve too?

As a matter of fact, there is. I've already written up the code to do this, as well as a few other things.

For the evolving, you're going to need this:
Spoiler:

I've also made another change to this code. I noticed that, theoretically, a player could try to manipulate the dynamicLevel by having one really strong Pokemon on their team, and then a bunch of weak ones, thus skewing the average to be much lower. I came up with a process that makes this tactic not work, or at least not work nearly as well.
Spoiler:

And that should just about do it! Hopefully I didn't miss anything. If you have any errors with this code, just let me know, and I'll try to help you out.
 
Last edited:
1
Posts
4
Years
  • Age 21
  • Seen Jan 23, 2022
hey, is there any chance you could send me a link to your battle_main.c? I am having a bit of trouble and it would be greatly appreciated.
 
247
Posts
6
Years
  • Age 25
  • Seen Apr 23, 2024
hey, is there any chance you could send me a link to your battle_main.c? I am having a bit of trouble and it would be greatly appreciated.

I've attached two versions of this file: the first one is what you can see here in my previous post. In the second one, I changed how the dynamicLevel accounts for player's who have a wide level spread on their team. It's a bit more strict. Choose whichever version you prefer.

How were you having problems with implementing this? It may be useful to know so that you can learn something for the future.
 

Attachments

  • battle_main.c
    209.4 KB · Views: 25
  • battle_main2.c
    209.4 KB · Views: 50
10
Posts
3
Years
  • Age 26
  • Seen Oct 22, 2023
I've attached two versions of this file: the first one is what you can see here in my previous post. In the second one, I changed how the dynamicLevel accounts for player's who have a wide level spread on their team. It's a bit more strict. Choose whichever version you prefer.

How were you having problems with implementing this? It may be useful to know so that you can learn something for the future.

Is it compatible with Egg's upgrade?
 
180
Posts
6
Years
  • Age 20
  • Seen Apr 15, 2024
Wow, WiserVisor and DdraigZek, that's amazing! I'm adding your changes to the main post and my project, for sure.
All the things you guys did would have taken me a lot to figure out and discover, so thank you! (im adding your credits, too!)
 

vxo

ROM Hacker
63
Posts
6
Years
And, above "gBattleTypeFlags |= gTrainers[trainerNum].doubleBattle;", paste this " dynamicLevel -= rand_diff + PartyLevelAdjust;"


You should be more specific with where this needs to be pasted, because "above" could be interpreted as:

Code:
dynamicLevel -= rand_diff + PartyLevelAdjust;
gBattleTypeFlags |= gTrainers[trainerNum].doubleBattle;


and not how it's intended to be:

Code:
            }
            }
            dynamicLevel -= rand_diff + PartyLevelAdjust;
        }
        
        gBattleTypeFlags |= gTrainers[trainerNum].doubleBattle;

Just a little mistake I made when implementing this, and I'm sure some others will have this issue as well.
 
17
Posts
2
Years
  • Age 23
  • Seen Dec 14, 2022
Is there any way to implement this with wild Pokemon? Want to make an area with wild Audino that adjust to your level for easy grinding but not sure if this code can be applied like that
 
247
Posts
6
Years
  • Age 25
  • Seen Apr 23, 2024
Is there any way to implement this with wild Pokemon? Want to make an area with wild Audino that adjust to your level for easy grinding but not sure if this code can be applied like that

You can apply something similar to src/wild_encounter.c. In the function TryGenerateWildMon, you can add a conditional statement to check to see if the current wild pokemon is an Audino and some other condition, and then use elements of this code to generate a dynamic level based on the player's party to create a new level for the wild pokemon.

If you want every wild Audino to be dynamically leveled, it might look something like this:
Spoiler:
 
17
Posts
2
Years
  • Age 23
  • Seen Dec 14, 2022
You can apply something similar to src/wild_encounter.c.

Thank you for this, it is very helpful. One last question, how do I test if the player is in a specific map? Suppose I wanted these Audino to only dynamically level in, say, Petalburg Woods--what conditional would I add to the if statement? Something like && MAPSEC_PETALBURG_WOODS == TRUE ? Not totally sure
 
247
Posts
6
Years
  • Age 25
  • Seen Apr 23, 2024
Thank you for this, it is very helpful. One last question, how do I test if the player is in a specific map? Suppose I wanted these Audino to only dynamically level in, say, Petalburg Woods--what conditional would I add to the if statement? Something like && MAPSEC_PETALBURG_WOODS == TRUE ? Not totally sure

When I initially wrote my previous post, I thought it wasn't really possible, since the map isn't passed in to the function. But as soon as I looked around for a couple seconds, I noticed you could use gMapHeader, which (I think) holds the current map data of wherever the player is.

If you wanted the Audino's in Petalburg Woods to be dynamically leveled, your if-statement would look something like:
Code:
if(wildMonInfo->wildPokemon[wildMonIndex].species == SPECIES_AUDINO && gMapHeader.regionMapSectionId == MAPSEC_PETALBURG_WOODS)
If I understand gMapHeader correctly, that should make all Audino's in Petalburg Woods have dynamic leveling. This a much better way of going about it then setting the max level to 100 and checking for that, so I'm glad you asked so I would be forced to a look for a better solution.
 
2
Posts
2
Years
  • Age 22
  • Seen Nov 18, 2023
Regarding the Evolution Feature, I've encountered an error mainly about the "gEvolutionTable" being undeclared.
Am using the pokeemerald expansion if that information helps.
 

Lunos

Random Uruguayan User
3,114
Posts
15
Years
Regarding the Evolution Feature, I've encountered an error mainly about the "gEvolutionTable" being undeclared.
Am using the pokeemerald expansion if that information helps.
I'll proceed to quote my post from another thread:
To solve your problem you just have to define gEvolutionTable as an extern in the file where you intend to make use of it.
In other words, throwing in a extern struct Evolution gEvolutionTable[][EVOS_PER_MON]; somewhere near the top at src/pokedex.c should fix it.

Ex:
JUFSN1B.png
Note: Naturally, in this case, you'll want to put the declaration in src/battle_main.c because that's where you're invoking gEvolutionTable here.
 
Back
Top