|Click here for more tutorials!|
The following tutorial is less of a drop-in module and more of a guide as to how you can implement multiple protagonists. Be forewarned: Pokemon Essentials is not built for multiple protagonists. There are a lot of functions that will need to be eschewed and there may be several unexpected results from having multiple protagonists. Please see the Limitations section below for more info.
Managing multiple protagonists is not a task for the faint of heart or clueless at coding. That said, I will attempt to explain everything as best as I can as I go.
This guide was written for Essentials version 16.1.
The Main Protagonist
Let me tell you a story about a boy named Marcus.
Marcus is a Youngster who is just starting out as a Trainer. His name was chosen, like many protagonists, by being asked of his gender and name by a senile professor. Marcus chose Squirtle as his starter pokemon, and about five minutes ago, he caught a Rattata in the tall grass. He doesn't have any badges yet, but he does have two pokemon in his pokedex, so that's something, right?
Now Marcus's information is stored in a special object of the
PokeBattle_Trainer type. The code that makes up this class of object resides in the script section of the same name. This object stores a lot of information about Marcus: it stores his name, his trainer id, his trainer type, his current outfit, his badges, his money, his pokedex stats (seen and owned pokemon), any shadow pokemon species he's caught, his current party, a couple of booleans indicating if he's picked up his pokedex and pokegear, and his language of origin. All of this data is stored together in the
Now what is missing from the above list? Well, Marcus's backpack seems to be missing, for one thing. Marcus's items are all stored in an object of the
PokemonBag class, which is defined in the PScreen_Bag script section. This object stores his items all neatly in pockets, and even keeps track of the item he has registered to the quick-select key. This data is stored in the
$PokemonBag global variable.
The contents of Marcus's Item PC and Pokemon Storage system are also missing from the above list, but we'll worry about those later.
Both of these global variables,
$PokemonBag, were created brand new when Oak asked for Marcus's name, in the
pbTrainerName function call. And they both get specially saved to the save file and loaded back up again when we continue the game. They make up the identity of the player protagonist.
The Second Protagonist
Now, unlike a lot of protagonists, Marcus isn't an only child. He has an older brother. Erm... what was his name again?
Now be careful here. We found out above that calling
pbTrainerName to get a trainer's name also assigns new objects to the
$PokemonBag global variables! It counts as starting a whole new game, and we don't want that! So don't use
pbTrainerName to get the second character's name! We're going to have to do a little bit of manual coding then.
def pbSecondTrainerName() # First, we're gonna need the second trainer's trainer type # For the sake of this tutorial, we're going to hardcore something trainertype = 2 # Trainer Type 2 corresponds to :POKEMONTRAINER_Brendan in an unmodified Essentials game. # We need to set the playerID to match the other protagonist, so when we call # pbEnterPlayerName below, the correct sprite will appear. We need to save the # old one off for restoration afterwards. oldid=$PokemonGlobal.playerID $PokemonGlobal.playerID=2 #Metadata ID of the second character # Note: I created a PlayerC in the Metadata.txt to correspond with playerID 2 # Let's ask the player for a name for the second player trname = pbEnterPlayerName(_INTL("Your brother's name?"),0,7) if trname=="" # If the player doesn't supply a name, feel free to provide a default # For right now, we're going to copy this from pbTrainerName # which gets the name of the player from their computer by default gender=pbGetTrainerTypeGender(trainertype) trname=pbSuggestTrainerName(gender) end # Ok, let's now create the second trainer! trainer2=PokeBattle_Trainer.new(trname,trainertype) bag2=PokemonBag.new # Set his trainer id to something other than the first protagonist's trainer2.setForeignID($Trainer) trainer2.metaID=$PokemonGlobal.playerID # We'll get to this later $PokemonGlobal.playerID=oldid return [trainer2, bag2] end
Now this is all well and good, but our brother character, whom the player has just named "Nick", has a lot of backstory on him. You see, Nick left home a couple of years ago. He has a bunch of badges from all sorts of different regions, and he's got a decent team on his belt as well. He's actually captured nearly every pokemon known to us right now, and he's hunting down the last few. So let's give him that stuff:
protag2 = pbSecondTrainerName() # Let's fill in some details about our second protagonist tr = protag2 # He's got 7 badges in the current region for i in 0...7 tr.badges[i] = true end # He's picked up his pokedex and pokegear tr.pokedex=true tr.pokegear=true # He's seen every pokemon! for i in 0..PBSpecies.maxValue tr.setSeen(i) end # ...well, except for a couple pokemon tr.seen[PBSpecies::ARCEUS] = false tr.seen[PBSpecies::VICTINI] = false # He's missing a few more pokemon in owned for i in 0..PBSpecies.maxValue next if i==PBSpecies::MEWTWO next if i>=PBSpecies::AZELF && i < PBSpecies::VICTINI next if i>PBSpecies::DURANT tr.setOwned(i) end # But he's got enough to cover any pokeball bills, certainly tr.money = 734350 # Speaking of pokeballs, let's give him some # Remember, the bag is a separate object: protag2.pbStoreItem(:ULTRABALL,6) protag2.pbStoreItem(:GREATBALL,20) protag2.pbStoreItem(:REPEATBALL,2) protag2.pbStoreItem(:PREMIERBALL,12) protag2.pbStoreItem(:MASTERBALL,1) # ...etc etc etc... # Now he needs a party pkmn=PokeBattle_Pokemon.new(:VENUSAUR,67,tr) pkmn.name="Seedmon" pkmn.exp+=6790 # Not needed, just making the exp bar fill a little bit more pkmn.happiness=212 pkmn.setItem(:MIRACLESEED) pkmn.giveRibbon(:REGIONALCHAMPION) pkmn.pbLearnMove(:SOLARBEAM) pkmn.obtainLevel=5 pkmn.obtainMap=4 #Pokemon Lab pkmn.ev=[105,85,85,65,105,65] pkmn.calcStats # Need to recalc stats after ev change tr.party.push(pkmn) # ...repeat for other mons...
Right, so with all of that done, we need some place to put his information. We can't store Nick's info in the
$PokemonBag variables, since that's currently holding Marcus's information. So we need to store this stuff somewhere else, somewhere which is going to be saved. We could store it in the global variables:
$game_variables = protag2 #Store the [trainer, bag] array
Or, if you're not fond of putting variables you can't access from events in the
$game_variables array (like myself), we can make a new variable to hold it in
class PokemonGlobalMetadata attr_accessor :otherPlayerCharacter end
And now we can store off Nick's information!
$PokemonGlobal.otherPlayerCharacter = protag2
Now Marcus and Nick are real close. The two would always have fun discussions about Pokemon and girls when they were younger and living under the same roof. When Nick went off to become a Trainer, Marcus saved up a bunch of money (with their parents' help, of course) to buy Nick a PokeGear so they could keep in touch while he was on the road. Marcus liked to call his brother up from the house phone and see what he was up to.
Marcus calling up Nick is a good excuse to switch over to Nick for a while. Let's first make a method to switch characters, which we can call from the phone event in Marcus's house.
def pbSwitchPlayerCharacters current = [$Trainer,$PokemonBag] other = $PokemonGlobal.otherPlayerCharacter $Trainer = other $PokemonBag = other $PokemonGlobal.otherPlayerCharacter = current $PokemonGlobal.playerID=$Trainer.metaID end
This swaps Marcus's data out of the
$PokemonBag variables and stores it where Nick's data was stored, while also moving Nick's data into the two global variables that define the current protagonist. We've now swapped characters!
Note the last line of the switch function. The metaID is the id of the player Metadata used by Essentials to load up the correct player graphics. If we didn't set the
playerID variable, it would look like we're still controlling Marcus!
Now let's make that phone event:
Now Nick doesn't mind sharing his PC space with Marcus; he wants to help his little bro out as much as he can even though he's so far away. And Marcus didn't mind much at first either: Nick left loads of items in storage that Marcus could take for his own use. But Marcus soon realized that most of the Pokemon Storage system was taken up by Pokemon he couldn't control, due to lacking the badges required to gain their respect. It became clear that Marcus really needed to get his own accounts, even if it did mean losing out on some of the cooler pokemon and items that Nick had.
The Pokemon Storage System is represented by an object of the
PokemonStorage class. This object stores the pokemon boxes as
PokemonBox objects, filled with pokemon and containing other metadata, such as box names and backgrounds. The player's boxes are stored in the global variable
The Item Storage System is represented by an object of the
PCItemStorage class. This object stores stacks of items in an array and has a hard limit of 50 item slots defined. The item storage is unique among the data structures we've talked about thus far, in that it isn't stored in its own global variable, but in a property of the
pcItemStorage. It's also unique in that the value is lazily allocated: the
$PokemonGlobal.pcItemStorage property is
nil up until the player pulls up the item PC for the very first time. This shouldn't cause us too many problems, as we're simply swapping variables around and not interacting with them, but it is a good thing to know if you decide to access the Item PC.
So to allow Marcus his own accounts on the PC, we're going to need to expand the scope of our previous functions:
def pbSecondTrainerName() # [...] return [trainer2, bag2, PokemonStorage.new, nil] #Add two more entries to the array: pokemon and item stores end
And also our switch function:
def pbSwitchPlayerCharacters current = [$Trainer,$PokemonBag,$PokemonStorage,$PokemonGlobal.pcItemStorage] other = $PokemonGlobal.otherPlayerCharacter $Trainer = other $PokemonBag = other $PokemonStorage = other $PokemonGlobal.pcItemStorage = other $PokemonGlobal.otherPlayerCharacter = current $PokemonGlobal.playerID=$Trainer.metaID end
Now, Marcus should have plenty of room to grow his own collection of pokemon!
Note: The player's mailbox is another separate object: an Array stored in
$PokemonGlobal.mailbox. You may wish to swap that around as well. I doubt Nick wants Marcus reading all the mail he's getting from his girlfriend in Hoenn!
You may have noticed in the images of our protagonists' trainer cards that the start date and the time played are still the same. This is because this data isn't tied to the trainer, but rather to the player. So even though Nick left home a couple years before Marcus, Nick's trainer card will still say he started on the same day as Marcus. If you wish to experiment in swapping that data out too, the date the player started the game is stored in
$PokemonGlobal.startTime as a
Like I said at the top of this tutorial, Essentials is not built with multiple protagonists in mind. There are several things left over when switching protagonists, because so many things are not stored with the trainer at all, including:
- if the player has the running shoes
- whether the player has a snag machine
- whether the player has seen the storage system creator
- which maps the player has visited (for Flying to)
- the last healing spot of the player (when blacking out)
- the steps of Repel the player has remaining
- the amount of soot stored in the sootsack
- the amount of coins stored in the coin case
- the pokedexes the player has unlocked
- the phone numbers the player has in his PokeGear
- the pokemon and egg the player currently has in the daycare
- the player's Triad card collection
- the contents of the purify chamber
All of the above are stored in various variables in
$PokemonGlobal. But you can't simply swap out the
$PokemonGlobal object, because it also contains many things that don't change with the player, such as information for berry plants growing in the overworld, the locations of roaming pokemon, and of course it is where we are storing the other protagonist! In fact, many drop-in additions to Essentials modify the
$PokemonGlobal object to store information that needs to be saved. So swapping it out is not a good idea.
Keep all of these things in mind when considering if you want to include multiple protagonists in your game. It's likely going to cause a lot of bugs if the second protagonist is allowed free-reign of all the features Essentials has to offer, so it is probably best to keep the scope of the second protagonist limited to small areas and certain situations. A good first step is keeping the second protagonist away from a PC. Make sure any fights your second protagonist enters are "canLose" fights, because otherwise the second protagonist may inadvertently end up at the last Pokemon Center, near a PC and likely away from the area you want him to be in entirely.
Aside: A fun bug I found while testing this stuff: Maps with names that have the player's name in it (eg, "\\PN's House") seem to become stuck using the name of the protagonist that first saw the map's name this play session. So even though I was Marcus as I was saving, it was still called "Nick's House" in the save menu. And when I loaded up the game again afterwards, it came up as "Marcus's House" in the load screen. Fix this by going to the Game_Map script section and going to the bottom. Find and replace the following:
ret.gsub!(/\\PN/,$Trainer.name) #Find this ret=ret.gsub(/\\PN/,$Trainer.name) #Replace it with this