CHAPTER 14

image

Basic Scripting Exercises in RMVXA

We have now finished our game. All that is left is to cover a few things that I didn’t cover in earlier chapters. While an in-depth look into Ruby scripting within RPG Maker VX Ace (RMVXA) is outside of the scope of this book, in this chapter, it would still be a good idea to cover the basics. Chapter 15 will cover other miscellanea that I did not feel fit within the overall narrative of our game and the book so far.

What Is Ruby?

Ruby is an object-oriented programming language that was created by Yukihiro Matsumoto in 1993. It is used within RMVXA in the form of the RGSS (Ruby Game Scripting System) and serves as the driving force behind this game-development engine. It is praised by programmers worldwide for its ease of use. The best part of all is that Ruby is open source (and thus costs the grand total of nothing to get; it can be downloaded freely). Learning how to use Ruby will help you greatly whenever you decide to start customizing your games with scripts.

What Is Object-Oriented Programming?

In a sentence: it is programming that uses code organized into objects as the building blocks for a desired result. We’ve been seeing examples of how Ruby (and, thus, object-oriented programming) works, in the few scripting exercises we tackled for our game. Here’s what we’ve actually touched upon already.

  • $ represents a global variable. That is, a value denoted with $ can be used anywhere within a project (e.g., our game).
  • @ denotes an instanced variable (also known as an object variable). Instanced variables are used within classes (such as Game_Interpreter).
  • $game_actors[n] (where n is the actor ID) is an array that contains several defining aspects of Actors in RMVXA, such as name, level, and nickname. You can reference any of the variables listed under the header Public Instance Variables in the Game_Actor class by using the format $game_actors[n].x (where x is the variable name).
  • An array is an ordered collection of objects.
  • You can call methods defined within the Script Editor in the damage formula box for items and skills.

As you can see, there are quite a few things we already know. During the course of this chapter, we’ll be tackling other simple things that we can do with only the most basic of Ruby experience. The first exercise will be changing how critical hits are calculated, so that they are influenced by the Luck stat of the attacker and the defender.

Critically Coded

As robust as RMVXA is right out of the box, it has a few quirks that bug me. Thankfully, you can tweak and edit such things within the Script Editor. This first exercise is very nearly a one-liner. All we need to do is find the code that governs critical hit rates. Let’s go to the Script Editor and run a search for “critical.” Quite a few results will pop up, but we’re interested in the entry that says “Game_Battler (487): * Calculate Critical Rate of Skill/Item.” Clicking it takes us to the following method:

#--------------------------------------------------------------------------
# * Calculate Critical Rate of Skill/Item
#--------------------------------------------------------------------------
def item_cri(user, item)
  item.damage.critical ? user.cri * (1 - cev) : 0
end

What we see here is a method (item_cri) that accepts two parameters when run. For the curious, user is defined in Scene_ItemBase, while item is defined in Game_BaseItem. As you may recall from earlier in the book, what we’re looking at right now is a ternary expression. The preceding text is equivalent to the following code. The question mark takes the place of the then, while the colon does the same for the else.

if item.damage.critical
then user.cri * (1-cev)
else 0

That’s the extent of this particular method. Basically, if the skill or item is capable of landing criticals, then the chance to land a critical is equal to the user’s critical hit chance multiplied by 1, minus the enemy’s critical evasion rate (the result is a percentage). Let’s create a new page in the Materials section of the Script Editor to hold the altered version of our item_cri method. Here’s the altered result:

class Game_Battler
def item_cri(user, item)
  item.damage.critical ? (user.cri + (user.luk * 0.002)) - (luk * 0.002)
    * (1 - cev) : 0
  end
end

In my version, I made it so that the luck of both the user and the target influence the ability to land a critical hit. The backward slash after (luk * 0.002) is used to tell RMVXA to read the next line as part of the previous one.

Image Note  Recall that weird things happen when we split a single expression over multiple lines, if the game decides to run at all. The backward slash tells RMVXA to run the next line as a direct continuation of the previous one, which, of course, helps prevent such weirdness.

For every 5 LUK that the user has, he/she gains 1% to his/her CRI. For every 5 LUK that the target has, the user loses 1% to his/her CRI. So, how can I be so certain that we are working with percentages? There are two good ways to prove it.

  1. Change the multiplier on user.luk to 1. You will notice that the attacker always lands critical hits, no matter how low its LUK is. If we were working with whole numbers, the attacker’s CRI would be equal to its LUK and not a guaranteed 100%.
  2. Note that CEV is expressed in the Database as a percentage, yet it is subtracted from 1 here. If we were working with whole numbers, you would have to subtract from 100 instead. 10% CEV will result in (1 - 0.10) and not (1 - 10), for example.

To be honest, it also helps that I tested this while initially attempting to figure it out and noticed how the attacker would land guaranteed critical hits when I had whole numbers for multipliers within the formula. So, another thing that I wanted to fix within RMVXA was the way that battle messages were expressed. Mainly, the ones that appear when damage is dealt or healing is performed. You’ve probably noticed that the default messages are a bit clunky, to put it mildly. How do we go about this? Welcome to the next section!

Coded Messages (in a Bottle)

What I seek to change is the messages that are displayed after an actor or an enemy performs a skill that has a damaging or healing effect. As those are possible results of actions taken, we can skim the list of classes and methods within the Script Editor, to see if that is covered by anything. As it turns out, this is covered in its very own class called Game_ActionResult. Now that we know that, we can head over there to take a look. As circumstances would have it, the three methods we are looking for are the very last ones of the entire class. Here’s the first of them:

#--------------------------------------------------------------------------
# * Get Text for HP Damage
#--------------------------------------------------------------------------
def hp_damage_text
  if @hp_drain > 0
    fmt = @battler.actor? ? Vocab::ActorDrain : Vocab::EnemyDrain
    sprintf(fmt, @battler.name, Vocab::hp, @hp_drain)
  elsif @hp_damage > 0
    fmt = @battler.actor? ? Vocab::ActorDamage : Vocab::EnemyDamage
    sprintf(fmt, @battler.name, @hp_damage)
  elsif @hp_damage < 0
    fmt = @battler.actor? ? Vocab::ActorRecovery : Vocab::EnemyRecovery
    sprintf(fmt, @battler.name, Vocab::hp, -hp_damage)
  else
    fmt = @battler.actor? ? Vocab::ActorNoDamage : Vocab::EnemyNoDamage
    sprintf(fmt, @battler.name)
  end
end

Dissecting the hp_damage_text Method

Although the larger parts of the method may look like alphabet soup at the moment, we can deduce the four action results handled by this method, based on the code expressions.

  • The initial if statement displays a message when the attacker uses a skill that drains HP. A draining attack heals the user for the same amount of HP that it deals to its target.
  • The first elsif statement is called when the attacker uses a skill that does HP damage.
  • The second elsif statement is relevant when the character uses a skill that heals HP.
  • The else statement is used for the niche case in which the attacker uses a skill that does no damage to its target.

With that established, let’s dissect the first statement. It is two lines long and says the following:

fmt = @battler.actor? ? Vocab::ActorDrain : Vocab::EnemyDrain
sprintf(fmt, @battler.name, Vocab::hp, @hp_drain)

Image Note  Before you start working on this section, don’t forget to insert a new page in Materials at the Script Editor and copy-paste the relevant methods as we require them. Direct alteration of code should always be avoided, if possible.

Our variable assignment in the first line (fmt) is a perfect example of a local variable. As its name implies, a local variable is only used wherever it is called. Thus, whatever is plugged into fmt is used only for this particular statement and then scrapped. @battler.actor? is an instance variable that returns true if the battler is an Actor. If he/she is an enemy, it will return false. If @battler.actor? is true, the ActorDrain text will be displayed; otherwise, the EnemyDrain text will be displayed. The second line uses a special method called sprintf. RMVXA’s help file has a very useful write-up on the method. In layman’s terms, you can use sprintf to fill in a predetermined sentence of text. Let’s go to the Vocab module and find ActorDrain. You’ll find it along with several other terms, so here are lines 51 through 67 of the Vocab module:

# Results for Actions on Actors
ActorDamage     = "%s took %s damage!"
ActorRecovery   = "%s recovered %s %s!"
ActorGain       = "%s gained %s %s!"
ActorLoss       = "%s lost %s %s!"
ActorDrain      = "%s was drained of %s %s!"
ActorNoDamage   = "%s took no damage!"
ActorNoHit      = "Miss! %s took no damage!"
# Results for Actions on Enemies
EnemyDamage     = "%s took %s damage!"
EnemyRecovery   = "%s recovered %s %s!"
EnemyGain       = "%s gained %s %s!"
EnemyLoss       = "%s lost %s %s!"
EnemyDrain      = "Drained %s %s from %s!"
EnemyNoDamage   = "%s took no damage!"
EnemyNoHit      = "Missed! %s took no damage!"

The preceding code has ActorDrain and EnemyDrain (kill two birds with one stone and all that). %s is used as an indicator within the sprintf method. To quote the help file for the %s indicator: Character strings are output. The sprintf method for our first statement uses three parameters (fmt is the sentence to be formatted and not a parameter per se). As you can see, both ActorDrain and EnemyDrain have three instances of %s. The method plugs in the value of its parameters, from left to right. So, the first %s in ActorDrain would be filled with @battler.name (the name of the actor or enemy, as appropriate); the second %s would house the current name of the HP stat (remember that you can switch the name of quite a few terms from the self-named tab in the Database); and the third %s would display @hp_drain (the amount of HP drained by the skill or item).

ActorDrain is used when an Actor is affected by a draining attack, whereas EnemyDrain is used when an enemy is hit by the same. Suppose Eric is hit with a draining attack that takes away 37 HP. The ActorDrain string would read as follows:

Eric was drained of HP 37.

Not the clunkiest statement, but we should invert the number and the HP term for sure. The EnemyDrain string, on the other hand, is terrible. Here’s a sample line from a Slime drained of 11 HP.

Drained Slime 11 from HP.

There’s a reason why I made it a point to figure out how to fix this. Your first instinct may be to just swap the location of the HP value and the number, as I noted in the previous paragraph. The problem in this initial if statement is that, if we do that, we still get the following for the EnemyDrain sentence:

Drained Slime HP from 11.

Closer, but still clunky. What we actually have to do is split both possibilities, such that RMVXA fills in the sentence, depending on whether it is ActorDrain or EnemyDrain. After all, the %s should not be filled out in the same order for both.

Tweaking the hp_damage_text Method

Without further ado, let’s tweak this method. Here is what the code for if @hp_drain > 0 should look like when tweaked:

def hp_damage_text
    if @hp_drain > 0
      fmt = @battler.actor? ? Vocab::ActorDrain : Vocab::EnemyDrain
      if fmt = Vocab::ActorDrain
        sprintf(fmt, @battler.name, @hp_drain, Vocab::hp)
      else
        sprintf(fmt, @hp_drain, Vocab::hp, @battler.name)
    end

As you can see, we split the single sprintf argument in two, based on whether the Vocab used is ActorDrain or EnemyDrain. In the first case, we flip the @hp_drain and Vocab::hp parameters. In the alternate case, we switch it all around. The previous example should now read like the following:

Drained 11 HP from Slime.

Victory! We have three more statements to go for this method. The next one uses the ActorDamage and EnemyDamage Vocabs. My copy-paste of part of the Vocab module (back on pages 4 and 5) is universally useful for what we’ll be doing here, so take a peek at the relevant variables.

ActorDamage = "%s took %s damage!"
EnemyDamage = "%s took %s damage!"

As you can see, both ActorDamage and EnemyDamage use the same sentence structure. The only thing that would change is the origin of @battler.name. Incidentally, that particular elsif statement is fine. It requires no alterations. The next elsif statement uses ActorRecovery and EnemyRecovery, which read as such:

ActorRecovery = %s recovered %s %s!"
EnemyRecovery = "%s recovered %s %s!"

Much like the previous case, both variables share the same sentence structure. Thus, they share the same textual hiccup. Let’s take the ActorRecovery sentence for example. The sprintf method passes @battler.name, Vocab::hp, and -hp_damage, in that order. If Eric is the Actor in question, and the value of -hp_damage is 20, it would read like this.

Eric recovered HP 20.

Thankfully, that statement is as easily fixed as swapping the order in which the parameters are plugged into the sentence, unlike the similar case back at the initial if statement. As the else statement is also correctly expressed, here’s the completely tweaked version of hp_damage_text:

class Game_ActionResult
  #--------------------------------------------------------------------------
  # * Get Text for HP Damage
  #--------------------------------------------------------------------------
  def hp_damage_text
    if @hp_drain > 0
      fmt = @battler.actor? ? Vocab::ActorDrain : Vocab::EnemyDrain
      if fmt = Vocab::ActorDrain
        sprintf(fmt, @battler.name, @hp_drain, Vocab::hp)
      else
        sprintf(fmt, @hp_drain, Vocab::hp, @battler.name)
    end
    elsif @hp_damage > 0
      fmt = @battler.actor? ? Vocab::ActorDamage : Vocab::EnemyDamage
      sprintf(fmt, @battler.name, @hp_damage)
    elsif @hp_damage < 0
      fmt = @battler.actor? ? Vocab::ActorRecovery : Vocab::EnemyRecovery
      sprintf(fmt, @battler.name, -hp_damage, Vocab::hp)
    else
      fmt = @battler.actor? ? Vocab::ActorNoDamage : Vocab::EnemyNoDamage
      sprintf(fmt, @battler.name)
    end
  end

Image Caution  Don’t forget the class definition at the very top of your script page!

One down, two more to go!

Dissecting the mp_damage_text Method

The second of the three action result display methods is mp_damage_text. You can see the relevant method code at the end of this page. The first thing to notice is that, with the sole difference of the specific parameters being passed, we have the same text issues that we had in the previous method. For example, the if statement will display the following text, we’ll use Eric, a Slime, and the number value of 50 for this.

When ActorDrain: Eric was drained of MP 50
When EnemyDrain: Drained Slime of 50 from MP.

Image Tip  Finding patterns in code will help you understand how things in RMVXA work. For everything else, RMVXA has a robust Ruby reference in the help file.

#--------------------------------------------------------------------------
# * Get Text for MP Damage
#--------------------------------------------------------------------------
def mp_damage_text
  if @mp_drain > 0
    fmt = @battler.actor? ? Vocab::ActorDrain : Vocab::EnemyDrain
    sprintf(fmt, @battler.name, Vocab::mp, @mp_drain)
  elsif @mp_damage > 0
    fmt = @battler.actor? ? Vocab::ActorLoss : Vocab::EnemyLoss
    sprintf(fmt, @battler.name, Vocab::mp, @mp_damage)
  elsif @mp_damage < 0
    fmt = @battler.actor? ? Vocab::ActorRecovery : Vocab::EnemyRecovery
    sprintf(fmt, @battler.name, Vocab::mp, -@mp_damage)
  else
    ""
  end
end

The only real difference between this method and the previous one, besides the changes in parameter names and the use of the ActorLoss and EnemyLoss vocabs, is the fact that the else statement has a pair of quotation marks instead of actual content. The reasoning behind this is actually very simple: hp_damage_text already covers the text displayed when a skill or item does no damage. Writing it out a second time in this method would be redundant.

Tweaking the mp_damage_text Method

Take a stab at tweaking this method, given what we have discussed up to now. Whenever you’re ready, check the following code to see the tweaked version.

#--------------------------------------------------------------------------
# * Get Text for MP Damage
#--------------------------------------------------------------------------
def mp_damage_text
  if @mp_drain > 0
    fmt = @battler.actor? ? Vocab::ActorDrain : Vocab::EnemyDrain
    if fmt = Vocab::ActorDrain
      sprintf(fmt, @battler.name, @mp_drain, Vocab::mp)
    else
      sprintf(fmt, @mp_drain, Vocab::mp, @battler.name)
  end
  elsif @mp_damage > 0
    fmt = @battler.actor? ? Vocab::ActorLoss : Vocab::EnemyLoss
    sprintf(fmt, @battler.name, @mp_damage, Vocab::mp)
  elsif @mp_damage < 0
    fmt = @battler.actor? ? Vocab::ActorRecovery : Vocab::EnemyRecovery
    sprintf(fmt, @battler.name, -@mp_damage, Vocab::mp)
  else
    ""
  end
end

The tp_damage_text Method

The final method is tp_damage_text, which covers what happens when a skill or item lowers or increases the TP of an actor or enemy. All we have to do here is flip the two latter parameters once again.

  #--------------------------------------------------------------------------
  # * Get Text for TP Damage
  #--------------------------------------------------------------------------
  def tp_damage_text
    if @tp_damage > 0
      fmt = @battler.actor? ? Vocab::ActorLoss : Vocab::EnemyLoss
      sprintf(fmt, @battler.name, Vocab::tp, @tp_damage)
    elsif @tp_damage < 0
      fmt = @battler.actor? ? Vocab::ActorGain : Vocab::EnemyGain
      sprintf(fmt, @battler.name, Vocab::tp, -@tp_damage)
    else
      ""
    end
  end
end

Once flipped, we get the following result:

  #--------------------------------------------------------------------------
  # * Get Text for TP Damage
  #--------------------------------------------------------------------------
  def tp_damage_text
    if @tp_damage > 0
      fmt = @battler.actor? ? Vocab::ActorLoss : Vocab::EnemyLoss
      sprintf(fmt, @battler.name, @tp_damage, Vocab::tp)
    elsif @tp_damage < 0
      fmt = @battler.actor? ? Vocab::ActorGain : Vocab::EnemyGain
      sprintf(fmt, @battler.name, -@tp_damage, Vocab::tp)
    else
      ""
    end
  end
end

Again, the else statement contains quotation marks, as the no damage situation is handled in hp_damage_text. Once you have all three of the tweaked methods set up in a script page in the Materials section, you are done! Let’s work on a much simpler exercise now.

Of TP and Their Preservation

I have to start this section by noting that it is bizarre that TP is never spelled out within RMVXA. I assume that TP is short for Technique Points (or Tech Points). Given that most default skills that require TP are weapons skills or empowered physical attacks, this seems to be a valid assumption. In any case, TP are different from MP in the following ways:

  • Each party member starts each battle with between 0 and 25 TP.
  • A character can gain TP from taking damage and skills that grant TP.
  • After each battle, the party’s TP is reset.
  • TP as a stat is not displayed anywhere in the default RMVXA game menu, only in battle. You would have to use a script to add a visible TP stat into the game’s menu.

But what if you want TP in your game to act like the Limit Break bar in Final Fantasy VII? Making TP a persistent resource is actually extremely easy but requires code tweaking, so it’s a perfect exercise for this chapter. As usual, we have to find where and how RMVXA handles TP. We know that TP is set at the start of battle and reset at the end of combat.

Searching for TP

Let’s run a search in the Script Editor for TP. Among the first sets of results that pop in, we can see the TP display messages that we tweaked in the previous exercise. Of particular interest is Game_BattlerBase (line 42), where we can see that Preserve TP is a special flag that can be set. Where? In weapons and armor. You can create pieces of equipment that grant their users the ability to carry over their TP values from battle to battle. Of course, that’s not what we came here for, so let’s scroll down some more in the search results.

The next item of interest is also in Game_BattlerBase (line 449). A click on that result leads us to the following snippet.

def preserve_tp?
  special_flag(FLAG_ID_PRESERVE_TP)

This is a method that allows us to set the flag to preserve TP. If you get the sudden urge to search for preserve_tp?, I applaud you. It is definitely the right way to go. Running a search for that particular term returns a mere three results. The latter two are in Game_Battler and are exactly what we’re looking for. The first of them is the on_battle_start method (line 785), which initializes a player’s TP at the start of each battle, unless the flag has been set.

def on_battle_start
  init_tp unless preserve_tp?

Image Note  You should always try not to edit the default code and use script pages for that purpose. That way, if your changes break something, you can just erase the offending code, and all is well.

The other result is the on_battle_end method, which covers things that are resolved at the end of a battle, such as states that expire on battle end.

def on_battle_end
  @result.clear
  remove_battle_states
  remove_all_buffs
  clear_actions
  clear_tp unless preserve_tp?
  appear

Tweaking the TP Preserve Methods

Now, after that is said and done, take a look at the following code for the tweaked methods that should be present within a new script page. I commented out the lines of code that need to be removed.

class Game_Battler

  def on_battle_start
    # init_tp unless preserve_tp?
  end

  def on_battle_end
    @result.clear
    remove_battle_states
    remove_all_buffs
    clear_actions
    # clear_tp unless preserve_tp?
    appear
  end
end

Play-test the game afterward and note the following:

  • Your party members start their first battle with 0 TP (init_tp handles the randomization of TP, but we removed it from on_battle_start).
  • More important, your party members should now be able to carry over their TP from battle to battle (as we removed clear_tp, which handles the removal of TP at the end of battle).

Pretty awesome, isn’t it?

Other TP Considerations

While we’re on the subject of TP, we also have the max_tp method in line 494 of Game_BattlerBase. You can increase or decrease the maximum amount of TP that a player can stockpile. You could have skills that require more than 100 TP to use, in theory. In practice, the Database enforces the cap on TP cost of skills and TP gain from skills.

Image Note  Play-testing an altered TP cap will promptly reveal that the bar still fills up as if the party member had a cap of 100 TP. To correct this graphical hiccup, you’ll want to take a look at tp_rate (line 529 of Game_Battler) and change the denominator in the expression to the same value as your maximum TP cap.

Also, if you increase the TP cap and then try to use the TP Gain effect, you’ll find that you still get the same amount of TP, despite the fact that the amount should change, based on the new cap. How can we rectify this? Go to the Script Editor and run a search for “effects.” The fifth entry should read as follows:

Game_Battler(10): # * Constants (Effects)

Clicking it will take you to a list of effects, as used in the Database and defined by RMVXA. The TP Gain effect is governed by EFFECT_GAIN_TP. Let’s keep going down this rabbit hole. Run another search using EFFECT_GAIN_TP as the term to find. This time, the second result is the one we want. It takes us to line 550 of Game_Battler, where we see that EFFECT_GAIN_TP calls a method named item_effect_gain_tp. Guess what we’re searching for next?

Image Note  This process of tracking down code may seem silly and/or inefficient, but it’s an awesome way to figure out how RMVXA handles the many things that make it tick.

A search for item_effect_gain_tp returns only two results. The first one is the very code we’re looking at right now. As it so happens, the other is the method code for item_effect_gain_tp.

#--------------------------------------------------------------------------
# * [TP Gain] Effect
#--------------------------------------------------------------------------
def item_effect_gain_tp(user, item, effect)
  value = effect.value1.to_i
  @result.tp_damage -= value
  @result.success = true if value != 0
  self.tp += value
end

The first line defines the recovery value gained through the TP Gain effect. Value will always be a number between 1 and 100. You’ll want to apply a multiplier to the variable assignment, so that it correlates correctly with your newly changed cap. Following are three examples. Keep in mind that we are working with values expressed in percentages.

(effect.value1.to_i)*0.5
(effect.value1.to_i)*2
(effect.value1.to_i)*10

The first expression is appropriate for a TP cap of 50. The second expression is appropriate for a cap of 200; the last is used for a cap of 1000.

Damage Floors Revisited

I covered damage floors all the way back in Chapter 4. There, I discussed that you can change the damage that such terrain causes, based on Regions. Terrain tags work even better, and you can differentiate between types of floor damage in that way as well. However, what happens if you have (to give a hyperbolic example) more than 20 different types of floor damage? First, have a flash from the past (Chapter 4, to be exact).

#--------------------------------------------------------------------------
# * Get Base Value for Floor Damage
#--------------------------------------------------------------------------
def basic_floor_damage
return 2 if $game_variables[3] == 1
return 5 if $game_variables[3] == 2
return 10 if $game_variables[3] == 3
return 25 if $game_variables[3] == 4
end

I gave a list of four values of $game_variables[3] (the variable used to hold the Region ID in the exercise). You would have to write 16 more expressions in the form return x if $game_variables[3] = y to meet our lofty goal. However, Ruby provides a neater way of handling such a situation. Enter the case method. Here’s what the RMVXA Ruby reference has to say on case: case expressions execute branching for a single expression via matching. While we’re on the subject of bringing back older exercises to expand upon, let’s repackage this one into a module. We’ll call the module FloorDamage. It will contain a method called value that will return a certain damage number, based on the value of our Region ID variable. Look at the following code to see case in action.

module FloorDamage
  module_function

  def value
    case $game_variables[3]
    when 1
      2
    when 2
      5
    when 3
      10
    when 4
      25
    else
      0
    end
  end
end

We declare a case parameter (in this case, our variable) and then stipulate various conditions, as necessary. I won’t actually expand this to 20 values of our variable, but you can probably already see that this can save a lot of time and space when used in the right situations.

Image Note  Instead of typing in module_function, an alternative way to declare module functionality is by using self for each of the methods contained within. So, you would have def self.value, were you to apply it to the preceding module. You do need to do one or the other, however, or your game will crash and return an error.

Game Over by Incapacitation

By default, the only way that a player will receive a game over is if his/her entire party is dead. However, not every role-playing game (RPG) throughout the years has followed such a system. Take the Final Fantasy games, for example. If the entire party is unable to act, that will result in a game over, even if the party is otherwise still alive. Petrification and Paralysis are two of the most common status effects that can cause this alternate game over. A look at the States tab reveals that none of the default movement-blocking states has infinite duration. Perhaps it was something that the designers thought of but decided against implementing. In any case, all we have to do is find the code that governs game overs and work from there. Running a search in the Script Editor for “game over” returns a few results. The third one (the only result that takes us to Scene_Base) is the one we’re looking for. Take a look at the code below.

  #--------------------------------------------------------------------------
  # * Determine if Game Is Over
  #   Transition to the game over screen if the entire party is dead.
  #--------------------------------------------------------------------------
  def check_gameover
    SceneManager.goto(Scene_Gameover) if $game_party.all_dead?
  end
end

This method is where our rabbit hole starts. Our next step is to find the all_dead? method in Game_Party and see what that entails. Running a search for that method turns up a rather curious result (see Figure 14-1).

9781484207857_Fig14-01.jpg

Figure 14-1. A screenshot of the third and fourth results returned after running a search for all_dead?

Namely, all_dead? is defined in two places. Actually, that’s not strictly true, as I’ll promptly explain. Clicking the Game_Unit result reveals the following method:

def all_dead?
alive_members.empty?

Now, let’s take a look at the Game_Party equivalent.

def all_dead?
super && ($game_party.in_battle || members.size > 0)

Why are they different? If you look at how the two classes are expressed within the Script Editor, they should look like this:

class Game_Unit
class Game_Party < Game_Unit

There is our answer. Game_Party is a subclass of Game_Unit. For an analogy, think of it as a parent and a child. Game_Unit is the parent of Game_Party. The super in Game_Party’s version of the method is a call to the parent method of the same name in Game_Unit. Execution-wise, this is technically what the Game_Party version actually says:

alive_members.empty? && ($game_party.in_battle || members.size > 0)

Image Note  super can also accept parameters to limit what is called from the parent method. However, in RMVXA, you’ll mostly be seeing super used without any parameters passed. In that case, it passes all of the method’s arguments up to the superclass’ method.

You may be wondering what the double “and” marks (ampersands) are for. They’re one of Ruby’s various operator expressions. You can find a full list of operators on RMVXA’s Operator Expressions reference page. The two I’m personally interested in within the context of the basics are the && and || operators. && is another way to express and, while || is the equivalent of or. The difference between using the symbol operators and the word operators is essentially summarized on the cited reference page. Here’s a relevant screenshot (Figure 14-2).

9781484207857_Fig14-02.jpg

Figure 14-2. A comparison of how Ruby handles a trio of variables, depending on where the operators are located

&& has a greater priority than ||. So, if you have an expression that you can count on executing in a certain order, it may be preferable to use and/or instead. Our next task is to find alive_members. As we want the method definition, what we’re looking for is on line 28 of Game_Unit.

def alive_members
  members.select {|member| member.alive? }

The expression contained within alive_members contains a new method, as it were. .select is part of Ruby’s Enumerable module. This particular method accepts an object (in this case, |member|) that will be analyzed, based on the block (member.alive?) criteria. This method returns an array containing every object within the list (members) for which the block returns true. In other words, the alive_members array will contain a list of every party member that is alive. Being alive is defined by the alive? method, which can be found in line 565 of Game_BattlerBase.

def alive?
  exist? && !death_state?

This method contains another new Ruby idiom. Notice that death_state? has an exclamation mark to its left. That mark declares a state of opposition. In other words, the alive? method checks for death_state? to be false rather than true. We have nearly reached the bottom of this rabbit hole. exist? is only used to determine whether a certain battler is hidden or not (and, thus, only really applies to enemies, as Actors in battle always exist). So, let’s just search for death_state? (note the absence of the exclamation mark). The very first result is what we have been looking for. As the comment box directly above the method notes, death_state? is used to check for the KO state.

def death_state?
  state?(death_state_id)

The method has a one-line expression that checks to see if the battler is afflicted with the Death state, as defined in the death_state_id method (the one right below death_state?, actually; it returns a value of 1). Trying to reinvent the wheel here is something you should only try when you gain greater expertise in Ruby. For now, tweaking that expression will suffice. The easiest way to do that is by using || or or to add new state? expressions.

Image Note  Either operator will work in this case.

So, suppose you wanted to make it so that Paralysis (state #7 in the default state list in RMVXA) counts as an incapacitating state. First, go to the States tab and change it, so that it can only be removed at the end of battle. Then, return to the death_state? method and tweak it like so:

def death_state?
  state?(death_state_id) || state?(7)

To test whether the change is working or not, you can give one of the existing enemies a Paralysis-inducing skill (or give their normal attacks a 100% Paralysis State Rate) and then run a Battle Test. If you get a Game Over screen after being afflicted by Paralysis, you’ll know that it is working correctly. Speaking of game overs, I have one last exercise to work through.

Adding a Menu to the Game Over Screen

This last exercise is going to involve some copy-pasting. Mainly, we’re going to copy the RMVXA code that handles the Title screen’s menu and integrate that into the class that governs the Game Over screen. The first order of business is finding the two classes. As it so happens, both the Title screen and the Game Over screen are governed by classes with the Scene prefix. Copy Scene_Title and Scene_Gameover to the Materials section of the Script Editor. Next, take a look at Scene_Gameover and analyze what is going on.

  • Scene_Gameover starts with a method (called start) that calls to its parent in Scene_Base by use of super. Next, it executes three methods of its own.
  • As a whole, the methods create the background that says “game over,” handle the fade-out after the party is defeated and the subsequent fade-in to the Game Over screen, and play the appropriate music.
  • There are several other methods that handle graphics processing within the class, but what we’re interested in is the transition to Title screen method (goto_title).

As we want the game to cause a menu to pop up at the Game Over screen, instead of just cutting to the Title, we might as well go ahead and excise what we won’t be using. Delete the goto_title method and then erase goto_title if Input.trigger?(:C) from the update method near the top of the class. With that done, we move over to Scene_Title. Our objective at the Scene_Title class is to figure out what methods are used to call up the Title screen’s menu. If we take a look at the start method for Scene_Title, we see that six methods are called up. Our method of interest is create_command_window. A closer look at it (it starts on line 91) shows the following code:

def create_command_window
  @command_window = Window_TitleCommand.new
  @command_window.set_handler(:new_game, method(:command_new_game))
  @command_window.set_handler(:continue, method(:command_continue))
  @command_window.set_handler(:shutdown, method(:command_shutdown))
end

The first expression in the method draws a new window on the screen. Then each of the next three expressions handles a menu option via the set_handler method (located in Window_Selectable). set_handler accepts two parameters. The first parameter is the handle and the second parameter is the method. For example, the first set_handler expression in create_command_window calls the command_new_game method when the :new_game handle is invoked. This will become relevant in the next section. For now, the result, as you’ve almost certainly seen countless times during play-testing, is the Title menu that has three options. So, we know that we want to draw the Title menu into the Game Over screen. The next question becomes: How is this method drawing the window to the screen? If we compare the start methods of Scene_Title and Scene_Gameover, we see that the game over method doesn’t draw a command window (create_command_window). You’ll want to copy lines 88 through 128 of Scene_Title to your altered Scene_Gameover method in the Materials section. Then, add create_command_window to Scene_Gameover’s start method. It should look like the following:

def start
  super
  play_gameover_music
  fadeout_frozen_graphics
  create_background
  create_command_window
end

With that out of the way, you’re done! Now, play-test your game and get into a situation that causes a game over. (It might be quicker to create an event that calls up the Game Over screen when you interact with it.) If you have followed these instructions, your Game Over screen should look like Figure 14-3.

9781484207857_Fig14-03.jpg

Figure 14-3. A screenshot of the Game Over screen, menu now included

If you just wanted to have the Title screen menu in your game over, you are done.

Tweaking the Game Over Menu

However, what if you wanted to tweak the menu? As mentioned before, the menu window and options are handled in create_command_window. You may be thinking that removing an option from this method is all you would have to do. That is actually not true. If you do that, you’ll still see the option in the game over menu. When you try to press the action button to get the option to do something, it will do nothing. Clearly, we must look elsewhere for the solution to this problem. The window itself is created and populated in Window_TitleCommand. However, altering that method will change the Title menu as well. Thus, you’ll want to do the following:

  1. Copy Window_TitleCommand to your Materials section.
  2. Rename the copied class Window_GameoverCommand.
  3. Hop over to your altered Scene_Gameover method and switch @command_window = Window_TitleCommand.new to @command_window = Window_GameoverCommand.new.
  4. Back at Window_GameoverCommand, find the make_command_list method (it’s near the bottom of the method).

The make_command_list method looks like this:

def make_command_list
  add_command(Vocab::new_game, :new_game)
  add_command(Vocab::continue, :continue, continue_enabled)
  add_command(Vocab::shutdown, :shutdown)
end

As you can see, this method is responsible for the three options that we see in the Title menu. To make the point, comment out (by typing in a single # before the line of code) the new_game command.. If you have followed my instructions correctly, the game over menu should now look like Figure 14-4.

9781484207857_Fig14-04.jpg

Figure 14-4. A screenshot of the game over menu after the changes

On the other hand, if you wanted to add a menu option to that list (say, for example, the ability to go back to the Title screen), you would be best served by finding the add_command method, to see how it works. You could run a search and find it that way, but look at the class definition for Window_GameoverCommand at the top of the script page, and you’ll see that this class is the child of Window_Command (this, of course, is also true in the case of Window_TitleCommand). Once at Window_Command, you needn’t look far, for the add_command method starts on line 61 of the class. Look at the following to see the method in question:

#--------------------------------------------------------------------------
# * Add Command
#     name    : Command name
#     symbol  : Corresponding symbol
#     enabled : Activation state flag
#     ext     : Arbitrary extended data
#--------------------------------------------------------------------------
def add_command(name, symbol, enabled = true, ext = nil)
  @list.push({:name=>name, :symbol=>symbol, :enabled=>enabled, :ext=>ext})
end

The comment box above the method is pretty good about explaining what parameter goes where. For most options, you’ll only be using the first two parameters. The command name is defined in the Vocab module (the specific terms list starts on line 113). The symbol used in the add_command method is the handle defined within set_handler. The Continue option also uses the third parameter, which determines whether or not you can select it from the menu. (When the player has no save files for the game, Continue will be grayed out.) .push is a method from Ruby’s Array class. As the method name implies, .push sends an object to the end of an array (the array, in this case, being @list). Going back to Window_GameoverCommand, we can see the format of an add_command object that accepts two parameters.

add_command(Vocab::shutdown, :shutdown)

Image Note  To summarize the menu logic: add_command adds options to menus while set_handler gives those options their functionality. As proven already, you need both to create a new menu option.

The Title screen option is called in the form to_title. So, you would write the following in make_command_list to add that command to Window_GameoverCommand.

add_command(Vocab::to_title, :to_title)

Place that one in between the two existent add_command expressions. Then, you would add the following line to create_command_window in Scene_Gameover, so that the option actually does something when the player interacts with it. I placed it between the Continue and Exit Game commands.

@command_window.set_handler(:to_title, method(:command_to_title))

Last, you need to add the command_to_title method itself, or the game will return an error telling you that the method is undefined.

Image Note  I named the method command_to_title, to keep with the naming conventions used for this command in RMVXA. As it turns out, there is a preexisting method of the same name. More on that below.

As said in the note above, there’s a preexisting method called command_to_title. In fact, running a search for the method will return only four results, two of them being the instances of the term we added for this exercise. The second result (line 48 of Scene_End) contains what we’re looking for. Here’s the method:

def command_to_title
  close_command_window
  fadeout_all
  SceneManager.goto(Scene_Title)
end

Copy-paste that method to the Scene_Gameover class you have tweaked, and we’re done! Play-test your game and cause a game over. If you have done everything correctly, the screen should look like Figure 14-5.

9781484207857_Fig14-05.jpg

Figure 14-5. A screenshot of the changed game over menu

In closing, I humbly urge you to continue exploring the depths of RMVXA’s Script Editor. The Appendix has a link to a list of scripts that can be used for RMVXA. If you want to investigate how something can be done, check to see if a script already exists for said functionality. There’s nothing wrong with picking the brains of those who have more experience than you.

Summary

During the course of this chapter, we performed several basic scripting exercises that involved tweaking parts of the Script Editor to achieve things that aren’t possible via eventing. Among them were causing a game over if the player’s entire party is incapacitated by another status effect that isn’t Death, tweaking the TP cap, and making sure that said tweaks were reflected properly within the game. In the final chapter of this book, I will cover miscellaneous concepts.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset