Foreign Pokémon in the Wild

Foreign Pokémon in the Wild

Pokémon Essentials Version
v18.1 ✅
Nobody ever talks about N's Pokémon, which is a shame, because I thought it was a really cool feature! This script lets you generate wild Pokémon with specific traits, including a different OT, then send them out into maps for the player to encounter in the wild!

Installing this resource
It's very nearly plug-and-play! (Technically, it's perfectly functional with just the one script, but there's a few flourishes to put in so that messages don't look goofy.

This is the main script, which you just paste as a new script section above Main:
class PokemonGlobalMetadata
  attr_accessor :foreignEncounter
  attr_accessor :foreignPokemon
  attr_writer   :foreignPokemonCaught

  def foreignPokemonCaught
    return @foreignPokemonCaught || []

# 0-5 are about encountering the Pokémon
#0-Species, 1-lvl, 2-switch, 3-encounter type, 4-battle BGM, 5-map ID
#Encounter types are the same as for Roaming Pokémon

#6-8 affect cosmetic info
#6-shiny (true/false), 7-gender (0M, 1F), 8- Nickname

#9-12 affect OT info
#9-name, 10-gender (0M,1F,2N), 11-ID#, 12-language
#The trainer ID# can be anywhere from 1 to 55555
#You can go higher, but it won't come out how you write it.
#(For example, 555555 doesn't display as "55555", but instead 31267)

#13-18 affect battle properties
#13-form, 14-nature, 15-IVs (an array of six numbers or one number),
#16-EVs (has to be six numbers), 17-moves (as an array), 18-ability#

#19 is currently reserved for items, but items sadly don't work yet.
#If you're using Pokémon Memories, you can uncomment part of the code
#and use 20 to set memory.

foreignSpecies = [
[:PIKACHU, 10, 8, 1, nil, 66, true, 0, "Pika", "Red", 0, 1, 1, 0, :HASTY,
[1,2,3,4,5,6], nil, [:TACKLE, :SPARK], 0],
[:BULBASAUR, 10, 8, 1, nil, 66, true, 0, "Saur", "Red", 0, 55554, 1, 0, :TIMID,
[31], nil, [:TACKLE, :FRENZYPLANT], 0]

# Encountering a foreign Pokémon in a wild battle.
class PokemonTemp
  attr_accessor :foreignIndex   # Index of foreign Pokémon to encounter next

# Returns whether the given category of encounter contains the actual encounter
# method that will occur in the player's current position.
def pbForeignMethodAllowed(encType)
  encounter = $PokemonEncounters.pbEncounterType
  case encType
  when 0   # Any encounter method (except triggered ones and Bug Contest)
    return true if encounter==EncounterTypes::Land ||
                   encounter==EncounterTypes::LandMorning ||
                   encounter==EncounterTypes::LandDay ||
                   encounter==EncounterTypes::LandNight ||
                   encounter==EncounterTypes::Water ||
  when 1   # Grass (except Bug Contest)/walking in caves only
    return true if encounter==EncounterTypes::Land ||
                   encounter==EncounterTypes::LandMorning ||
                   encounter==EncounterTypes::LandDay ||
                   encounter==EncounterTypes::LandNight ||
  when 2   # Surfing only
    return true if encounter==EncounterTypes::Water
  when 3   # Fishing only
    return true if encounter==EncounterTypes::OldRod ||
                   encounter==EncounterTypes::GoodRod ||
  when 4   # Water-based only
    return true if encounter==EncounterTypes::Water ||
                   encounter==EncounterTypes::OldRod ||
                   encounter==EncounterTypes::GoodRod ||
  return false

EncounterModifier.register(proc { |encounter|
  $PokemonTemp.foreignIndex = nil
  next nil if !encounter
  # Give the regular encounter if encountering a foreign Pokémon isn't possible
  next encounter if $PokemonGlobal.partner
  next encounter if $PokemonTemp.pokeradar
  next encounter if rand(100)<50   # 50% chance of encountering a foreign Pokémon
  # Look at each foreign Pokémon in turn and decide whether it's possible to
  # encounter it
  $PokemonGlobal.foreignPokemon = [] if !$PokemonGlobal.foreignPokemon
  foreignChoices = []
  for i in 0...foreignSpecies.length
    # [species symbol, level, Game Switch, encounter type, battle BGM, area maps hash]
    foreignData = foreignSpecies[i]
    next if $PokemonGlobal.foreignPokemon[i]==true   # Foreign Pokémon has been caught
    # Ensure species is a number rather than a string/symbol
    species = getID(PBSpecies,foreignData[0])
    next if !species || species<=0
    # Makes sure the relevant switch is on
    next if $game_switches[switch]==false
    # Get the foreign Pokémon's map
    # Check if foreign Pokémon is on the current map. If not, check if foreign
    # Pokémon is on a map with the same name as the current map and both maps
    # are in the same region
    if foreignMap!=$game_map.map_id
      currentRegion = pbGetCurrentRegion
      next if pbGetMetadata(foreignMap,MetadataMapPosition)[0]!=currentRegion
      currentMapName = pbGetMessage(MessageTypes::MapNames,$game_map.map_id)
      next if pbGetMessage(MessageTypes::MapNames,foreignMap)!=currentMapName
    # Check whether the roaming Pokémon's category of encounter is currently possible
    next if !pbForeignMethodAllowed(foreignData[3])
    # Add this foreign Pokémon to the list of possible foreign Pokémon to encounter
  # No encounterable roaming Pokémon were found, just have the regular encounter
  next encounter if foreignChoices.length==0
  # Pick a roaming Pokémon to encounter out of those available
  chosenForeign = foreignChoices[rand(foreignChoices.length)]
  $PokemonGlobal.foreignEncounter = chosenForeign
  $PokemonTemp.foreignIndex     = chosenForeign[0]   # Foreign Pokémon's index
  if chosenForeign[3] && chosenForeign[3]!=""
    $PokemonGlobal.nextBattleBGM = chosenForeign[3]
  $PokemonTemp.forceSingleBattle = true
  next [chosenForeign[1],chosenForeign[2]]   # Species, level

Events.onWildBattleOverride += proc { |_sender,e|
  species = e[0]
  level   = e[1]
  handled = e[2]
  next if handled[0]!=nil
  next if !$PokemonGlobal.foreignEncounter
  next if $PokemonTemp.foreignIndex==nil
  handled[0] = pbForeignPokemonBattle(species,level)

def pbForeignPokemonBattle(species, level)
  # Get the foreign Pokémon to encounter; generate it based on the species and
  # level if it doesn't already exist
  idxForeign = $PokemonTemp.foreignIndex
  if !$PokemonGlobal.foreignPokemon[idxForeign] ||
    $PokemonGlobal.foreignPokemon[idxForeign] = pbGenerateWildPokemon(species,level,false)
      #Edits to Pokemon
    case chosenForeign[6]
    when true
    when false
    case chosenForeign[7]
     when 0
      when 1
    if chosenForeign[8] && chosenForeign[8]!=""[8]
   #OT protperties
   if chosenForeign[9] && chosenForeign[9]!=""
    if chosenForeign[10] && chosenForeign[10]!=""
    if chosenForeign[11] && chosenForeign[11]!=""
    if chosenForeign[12] && chosenForeign[12]!=""
    #battle properties
   if chosenForeign[13] && chosenForeign[13]!=""
    if chosenForeign[14] && chosenForeign[14]!=""
    if chosenForeign[15] && chosenForeign[15]!=""
      case newIVs.length
        when 1
      when 6
    if chosenForeign[16] && chosenForeign[16]!=""
    if chosenForeign[17] && chosenForeign[17]!=""
      for i in 0...newMoves.length
   if chosenForeign[18] && chosenForeign[18]!=""
  # if chosenForeign[20] && chosenForeign[20]!=""
     # $PokemonGlobal.foreignPokemon[idxForeign].memory=chosenForeign[20]
  # end
  # Set some battle rules
  # Perform the battle
  decision = pbWildBattleCore($PokemonGlobal.foreignPokemon[idxForeign])
  # Update Foreign Pokémon data based on result of battle
  if decision==1 || decision==4   # Defeated or caught
    $PokemonGlobal.foreignPokemon[idxForeign]       = true
    $PokemonGlobal.foreignPokemonCaught[idxForeign] = (decision==4)
  $PokemonGlobal.foreignEncounter = nil
  # Used by the Poké Radar to update/break the chain
  # Return false if the player lost or drew the battle, and true if any other result
  return (decision!=2 && decision!=5)

EncounterModifier.registerEncounterEnd(proc {
  $PokemonTemp.foreignIndex = nil

In PokeBattle_BattleCommon, find
  def pbStorePokemon(pkmn)
    # Nickname the Pokémon (unless it's a Shadow Pokémon)
    if !pkmn.shadowPokemon?
Below that, add
      if pkmn.isForeign?
        pbDisplayPaused(_INTL("It seems like {1} has a different OT!.",
Alternatively, if your game allows renaming Pokémon that aren't yours, you could change it to:
  def pbStorePokemon(pkmn)
    # Nickname the Pokémon (unless it's a Shadow or foreign Pokémon)
    if !pkmn.shadowPokemon? && !pkmn.isForeign?
To get around the awkwardness of "Give a nickname to this Pokémon that clearly has a nickname?"

Below that, find
pbDisplayPaused(_INTL("{1}'s data was added to the Pokédex.",
Change it to:
          pbDisplayPaused(_INTL("{1}'s data was added to the Pokédex.",PBSpecies.getName(pkmn.species)))
This makes sure the game says "Added (species)'s data to the PokeDex", rather than saying "Added (nickname)'s data to the PokeDex".

In Battle_StartAndEnd, find
    if wildBattle?
      foeParty = pbParty(1)
      case foeParty.length
      when 1
          pbDisplayPaused(_INTL("Oh! A wild {1} appeared!",foeParty[0].name))
Change "pbDisplayPaused(_INTL("Oh! A wild {1} appeared!",foeParty[0].name))" to
        if $PokemonTemp.foreignIndex
          pbDisplayPaused(_INTL("Huh? {1} appeared!",foeParty[0].name))
          pbDisplayPaused(_INTL("Oh! A wild {1} appeared!",foeParty[0].name))
This will make battles display "Huh? (Pokémon) appeared!" for Pokémon generated with this script, while regular encounters still say "Oh! A wild (Pokémon) appeared!"

Using this resource
(When you release your game, don't forget to remove my example Pokémon!)

So, there's a lot that you can define for these Pokémon. (I tried to make pretty much every trait of a Pokémon editable through this script, so it could be easier to set up)

To add a Pokémon to this, just find this section at the top:
foreignSpecies = [
[:PIKACHU, 10, 8, 1, nil, 66, true, 0, "Pika", "Red", 0, 1, 1, 0, :HASTY,
[1,2,3,4,5,6], nil, [:TACKLE, :SPARK], 0],
[:BULBASAUR, 10, 8, 1, nil, 66, true, 0, "Saur", "Red", 0, 55554, 1, 0, :TIMID,
[31], nil, [:TACKLE, :FRENZYPLANT], 0]
You're going to create a new entry similar to what I've put here! There's a lot to these examples, but you actually only need the first six entries, the rest are all optional! Set any of them to nil, and it'll be generated the same as any other wild Pokémon!

0-5- Encountering the Pokémon
  • 0 is the species, written as :SPECIES
  • 1 is the level the Pokémon will be when encountered
  • 2 is the number of the Global Switch that needs to be turned on for this encounter to happen
  • 3 is the encounter type the Pokémon will spawn in. It's the same as it is for Roaming Pokémon :
    • 0 - Walking in grass, walking in caves or surfing.
    • 1 - Walking in grass or walking in caves.
    • 2 - Surfing.
    • 3 - Fishing.
    • 4 - Surfing or fishing.
  • 4 is a filename for BGM for this encounter, formatted as "name", if you'd like to change it from the standard encounters on the map
  • 5 is the ID number of the map you can encounter this Pokémon on
6-8- Cosmetic info
  • 6 is true if the Pokemon is a guaranteed Shiny and false if it's Shiny-locked. Set it to nil if you want the regular shiny chance.
  • 7 is the gender of the Pokémon - 0 for male, 1 for female. You can leave it as nil to have the regular gender chance, and genderless Pokémon should also have it set to nil, because it might cause an error otherwise.
  • 8 is the nickname of the Pokemon, formatted as "Name". Leave as nil to have the regular species name.
9-12- OT's info

  • 9 is the name of the OT, written as "Name". Leave as nil to let the player be defined as the OT for this Pokémon. If you do, make sure anything else you use in this section is nil, too.
  • 10 is the gender of the OT- 0 for male, 1 for female, 2 for other.
  • 11 is the ID number of the OT. As far as I can tell, you can write in any number from 1 to 55555 and have it display those characters. Any higher and I think it calculates it differently somehow, so the digits won't display what you typed in.
  • 12 is the OT's language.
    • 0 = Unknown
    • 1 = Japanese
    • 2 = English
    • 3 = French
    • 4 = Italian
    • 5 = German
    • 7 = Spanish
    • 8 = Korean

13-18 Battle Properties
  • 13 is the number of this Pokémon's form.
  • 14 is the nature of the Pokémon, written as :NATURE
  • 15 is this Pokémon's IVs. You can either make this an array of six numbers, like [1,2,3,4,5,6], or an array of one number, like [1]. If it's just one number, then all six IVs will be set to that one number.
  • 16 is this Pokémon's EVs. This one has to be an array of six numbers, because I didn't see the point in setting EVs to the same thing when you could only go up to 42 doing it that way.
  • 17 is this Pokémon's moves. You have to set this an array, but it can be as long or short as you like- [:MOVE] to [:MOVE,:MOVE,:MOVE,:MOVE]. This script uses pbLearnMove to add these in, so it'll bump off the top move if the Pokémon has four moves, but the Pokémon will still have any remaining natural moves in its learnset.
  • 18- The number of this Pokémon's ability slot.

19 is currently reserved for items, but I unfortunately don't have it working yet.

20 is designed to work with my Pokémon Memories script! It's commented out so this resource doesn't cause any crashes on its own, but if you'd like to use them together, just remove the #s you see blocking it, and it should be pretty simply to use! Just use "text" to write the memory!

I had considered making probability an element of the Pokémon, but I couldn't get that to run smoothly. Right now, probability is defined by this line here:
  next encounter if rand(100)<50   # 50% chance of encountering a foreign Pokémon

That means that the game has a 50% chance of checking for a foreign Pokémon with every encounter.

If you'd like this to change depending on what Pokémon is being found, just add some conditionals around it! For example, if I want one specific Pokémon to be rarer than the others, I'd just change this to:

if $game_switches[switch]==true && $game_map.map_id==number
  next encounter if rand(100)<90   # 10% chance of encountering a foreign Pokémon
  next encounter if rand(100)<50   # 50% chance of encountering a foreign Pokémon

And just make "switch" and "number" match up with the ones for my Pokémon!

Ideas for this resource
  • Naturally, reformed villain's Pokémon out in the wild would be fun to catch! But it doesn't have to be villains- why not give some iconic characters a released Pokémon for the player to find? (The TCG's Owner's Pokémon could be a good source of inspiration!)
  • But then, who says it has to be a character the player's met at all? There's a lot of scenarios where a player could find a long-gone trainer's Pokémon- an ancient king's team, a mad scientist's experiments gone rogue, a fallen adventurer's Pokémon- there's lot of ways the player could uncover a mysterious past!
  • You don't even have to have an OT for these Pokémon, though, you could just make some special Pokémon for the player to find! A guaranteed shiny, high IVs, egg moves, maybe even a special species or form you can only catch once!
  • This can also be used to replicate Ultra Beast encounters from the postgame Looker missions!

Known Issues
Unfortunately, I can't manage to get items set in this script. (I think the way the game handles wild items is throwing me off) I'll update this when I get a fix!

Future Goals
  • Create the animation that plays for N's Pokémon
  • Create a way to generate a random foreign ID, so you don't have to create an ID for each character.
  • Create a way to guarantee X number of perfect IVs without requiring the specific stat to be set
  • Fix the issue of being unable to set items.
  • Make sure nothing tricky can go on with setting EVs.
  • Create a limit to the length of memories.
  • Figure out a way to combine this with phenomena, so it's more clear to the player that there's special Pokémon here.
  • I might add options for Pokerus, Ribbons, and Contest stats.
  • If I go genuinely crazy, I might consider working out a way to have this collect the data of released Pokémon and have them appear in the wild. That's a big if, though, I'm just putting it here so I remember the idea.
Credits to TechSkylander1518, please! And credit to the original devs of Pokémon Essentials, because the code for Roaming Pokémon was used as a base for a lot of this!
First release
Last update
0.00 star(s) 0 ratings

More resources from TechSkylander1518