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:
Ruby:
class PokemonGlobalMetadata
  attr_accessor :foreignEncounter
  attr_accessor :foreignPokemon
  attr_writer   :foreignPokemonCaught

  def foreignPokemonCaught
    return @foreignPokemonCaught || []
  end
end

# 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
end



# 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 ||
                   encounter==EncounterTypes::Cave
  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 ||
                   encounter==EncounterTypes::Cave
  when 2   # Surfing only
    return true if encounter==EncounterTypes::Water
  when 3   # Fishing only
    return true if encounter==EncounterTypes::OldRod ||
                   encounter==EncounterTypes::GoodRod ||
                   encounter==EncounterTypes::SuperRod
  when 4   # Water-based only
    return true if encounter==EncounterTypes::Water ||
                   encounter==EncounterTypes::OldRod ||
                   encounter==EncounterTypes::GoodRod ||
                   encounter==EncounterTypes::SuperRod
  end
  return false
end

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
    switch=foreignData[2]
    next if $game_switches[switch]==false
    # Get the foreign Pokémon's map
    foreignMap=foreignData[5]
    # 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
    end
    # 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
    foreignChoices.push([i,species,foreignData[1],foreignData[4],0,0,
    foreignData[6],foreignData[7],foreignData[8],foreignData[9],foreignData[10],
    foreignData[11],foreignData[12],foreignData[13],foreignData[14],foreignData[15],
    foreignData[16],foreignData[17],foreignData[18],foreignData[19]#,foreignData[20]
    ])
  end
  # 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]
  end
  $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].is_a?(PokeBattle_Pokemon)
    $PokemonGlobal.foreignPokemon[idxForeign] = pbGenerateWildPokemon(species,level,false)
  end
      #Edits to Pokemon
  chosenForeign=$PokemonGlobal.foreignEncounter
  pokemon=$PokemonGlobal.foreignPokemon[idxForeign]
  #Cosmetics
    #shiny
    case chosenForeign[6]
    when true
      pokemon.makeShiny
    when false
      pokemon.makeNotShiny
  end
    #gender
    case chosenForeign[7]
     when 0
      pokemon.makeMale
      when 1
      pokemon.makeFemale
    end
      #name
    if chosenForeign[8] && chosenForeign[8]!=""
     pokemon.name=chosenForeign[8]
    end
   #OT protperties
     #name
   if chosenForeign[9] && chosenForeign[9]!=""
      pokemon.ot=chosenForeign[9]
    end
      #gender
    if chosenForeign[10] && chosenForeign[10]!=""
      pokemon.otgender=chosenForeign[10]
    end
      #ID
    if chosenForeign[11] && chosenForeign[11]!=""
      pokemon.trainerID=chosenForeign[11]
    end
      #language
    if chosenForeign[12] && chosenForeign[12]!=""
      pokemon.language=chosenForeign[12]
    end
    #battle properties
      #form
   if chosenForeign[13] && chosenForeign[13]!=""
      pokemon.form=chosenForeign[13]
    end
    #nature
    if chosenForeign[14] && chosenForeign[14]!=""
        pokemon.setNature(chosenForeign[14])
      end
      #ivs
    if chosenForeign[15] && chosenForeign[15]!=""
      newIVs=chosenForeign[15]
      case newIVs.length
        when 1
          newIV=newIVs[0]
          pokemon.iv[PBStats::HP]=newIV
          pokemon.iv[PBStats::ATTACK]=newIV
          pokemon.iv[PBStats::DEFENSE]=newIV
          pokemon.iv[PBStats::SPATK]=newIV
          pokemon.iv[PBStats::SPDEF]=newIV
          pokemon.iv[PBStats::SPEED]=newIV
      when 6
          $PokemonGlobal.foreignPokemon[idxForeign].iv[PBStats::HP]=newIVs[0]
          $PokemonGlobal.foreignPokemon[idxForeign].iv[PBStats::ATTACK]=newIVs[1]
          $PokemonGlobal.foreignPokemon[idxForeign].iv[PBStats::DEFENSE]=newIVs[2]
          $PokemonGlobal.foreignPokemon[idxForeign].iv[PBStats::SPATK]=newIVs[3]
          $PokemonGlobal.foreignPokemon[idxForeign].iv[PBStats::SPDEF]=newIVs[4]
          $PokemonGlobal.foreignPokemon[idxForeign].iv[PBStats::SPEED]=newIVs[5]
      end
    end
    if chosenForeign[16] && chosenForeign[16]!=""
      newEVs=chosenForeign[16]
      pokemon.ev[PBStats::HP]=newEVs[0]
      pokemon.ev[PBStats::ATTACK]=newEVs[1]
      pokemon.ev[PBStats::DEFENSE]=newEVs[2]
      pokemon.ev[PBStats::SPATK]=newEVs[3]
      pokemon.ev[PBStats::SPDEF]=newEVs[4]
      pokemon.ev[PBStats::SPEED]=newEVs[5]
    end
    if chosenForeign[17] && chosenForeign[17]!=""
      newMoves=chosenForeign[17]
      for i in 0...newMoves.length
        move=newMoves[i]
        pokemon.pbLearnMove(move)
      end
    end
   if chosenForeign[18] && chosenForeign[18]!=""
     pokemon.setAbility(chosenForeign[18])
    end
  #memories
  # if chosenForeign[20] && chosenForeign[20]!=""
     # $PokemonGlobal.foreignPokemon[idxForeign].memory=chosenForeign[20]
  # end
  $PokemonGlobal.foreignPokemon[idxForeign]=pokemon
  # Set some battle rules
  setBattleRule("single")
  # 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)
  end
  $PokemonGlobal.foreignEncounter = nil
  # Used by the Poké Radar to update/break the chain
  Events.onWildBattleEnd.trigger(nil,species,level,decision)
  # Return false if the player lost or drew the battle, and true if any other result
  return (decision!=2 && decision!=5)
end

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

In PokeBattle_BattleCommon, find
Ruby:
  def pbStorePokemon(pkmn)
    # Nickname the Pokémon (unless it's a Shadow Pokémon)
    if !pkmn.shadowPokemon?
Below that, add
Ruby:
      if pkmn.isForeign?
        pbDisplayPaused(_INTL("It seems like {1} has a different OT!.",pkmn.name))
      end
Alternatively, if your game allows renaming Pokémon that aren't yours, you could change it to:
Ruby:
  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
Ruby:
pbDisplayPaused(_INTL("{1}'s data was added to the Pokédex.",pkmn.name))
Change it to:
Ruby:
          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
Ruby:
    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
Ruby:
        if $PokemonTemp.foreignIndex
          pbDisplayPaused(_INTL("Huh? {1} appeared!",foeParty[0].name))
        else
          pbDisplayPaused(_INTL("Oh! A wild {1} appeared!",foeParty[0].name))
        end
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:
Ruby:
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.

Other
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!

Probability
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:
Ruby:
  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:

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

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
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!
Author
TechSkylander1518
Views
952
First release
Last update
Rating
0.00 star(s) 0 ratings

More resources from TechSkylander1518

Top