Intro to Scripting 2 with Tim

in Tutorials

We looked at how to make a simple change by working through the code base, and determining where the change needs to be made.  However, going into the default code and making changes to it has a few drawbacks… it’s sloppy, not easily transportable, and a bit error prone.  So, how do we do better?

The Ruby language supports a few interesting things that are relatively unique among programming languages: redefinition, and aliasing.  This is one topic where it’s worth a bit of discussion about the language specifically, since these features are so important and relevant to our specific use case in writing scripts.

First, with redefinition, a class definition can be reopened at any time; at that time, new things can be added or replaced.  Consider the following example:

class MyClass
   def my_method
      #do something
    end
end

# all sorts of other stuff here

class MyClass
   def my_new_method
      #do something else instead
   end
end

my_object = MyClass.new
my_object.my_method       # Does "something"
my_object.my_new_method   # Does "something else"

Notice how we can easily add a new method to our class? But, there’s just one problem with this: my_new_method will only ever be called if we call it explicitly. Any existing code that calls my_method will still get the original. Well, we could instead reopen our class and replace my_method. But, suppose this happened multiple times, or even by multiple different programmers working on the same project? (Think of an RPG Maker project in which one has added scripts from multiple sources) Then, only the last definition has any effect. Clearly, this isn’t very good.

That’s where aliasing comes in. Aliasing allows us to keep a reference to the old method, while defining a replacement. Confused? To put it simply, this means that we still retain the ability to run the original method, even though we have replaced it with a new method, as in the following:

class MyClass
   def my_method
      #do something
   end
end

class MyClass
   alias my_old_method my_method   # Keeps a reference to the original  

   def my_method
      my_old_method
      # Do some additional things
   end
end

my_object = MyClass.new
my_object.my_method       # Does "something", and some new things

Note that, in the above example, our redefinition could work the opposite way as well; it could do some new things, then call the old method.  Now, we still have a slight issue in that if some other piece of code were to come along and use the same alias of “my_old_method”, we’d have a problem.

This is why scripters frequently will use long obscure names for aliases, or names that encompass the author and the function of the new script.  Doing so avoids the likelihood of a conflict.  So, going back to our “encounters while in the boat” example, we can now write it like this:

class Game_Player
   alias tim_boatencounters_update_encounter update_encounter
   def update_encounter
      tim_boatencounters_update_encounter
      @encounter_count -= 1 if @vehicle_type == 0
   end
end

What this now does is redefines the update_encounter() method, but does so in such a way as to first perform the existing functionality, then after, do something special if we are riding in the boat.  Note also, that if we wanted to make encounters more frequent while in the boat, we could use a value greater than 1 (similar to how the default scripts handle when you’re in bush areas).

Now that we’ve cleaned up our code a bit for boat encounters, let’s look at another simple example. Suppose we want to add some challenge to our game, by limiting the party to holding no more than, say, 5 of a certain item. Something like this might be useful for health or mana potions, where a larger number might make some battles too easy, perhaps.  We need to figure out where something like this is even handled.

If we are savvy at eventing, we already know that there is an event command for adding (or losing) an item. Thinking back to a tip from the first article, that means looking in Game_Interpreter is a great place to start, so we can see how the “Change Items” event command is handled in scripts. It’s very easy to find the relevant code snippet in Game_Interpreter:

#--------------------------------------------------------------------------
# * Change Items
#--------------------------------------------------------------------------
def command_126
   value = operate_value(@params[1], @params[2], @params[3])
   $game_party.gain_item($data_items[@params[0]], value)
   $game_map.need_refresh = true
   return true
end

Again, it’s easy to see where to look next; the $game_party.gain_item method should have what we want. So, we go find it, and sure enough, it does:

def gain_item(item, n, include_equip = false)
   number = item_number(item)
   case item
   when RPG::Item
      @items[item.id] = [[number + n, 0].max, 99].min
   when RPG::Weapon
      @weapons[item.id] = [[number + n, 0].max, 99].min
   when RPG::Armor
      @armors[item.id] = [[number + n, 0].max, 99].min
   end
   n += number
   if include_equip and n < 0
      for actor in members
         while n < 0 and actor.equips.include?(item)
            actor.discard_equip(item)
            n += 1
         end
      end
   end
end

Carefully look over the code, and you can see that the maximum number of an item held is 99.  We need to change that, but only for certain items.  So, we will need to write some code that has a list of items, and how to handle them; let’s do it the right way as we learned above:

class Game_Party
   LIMITED_ITEM_IDS = [1, 2, 8, 10]  # For example; replace with item ids as needed

   alias tim_itemlimit_gain_item gain_item
   def gain_item(item, n, include_equip = false)
      tim_itemlimit_gain_item(item, n, include_equip)
      # Put code here to do what we want
   end
end

So, this is a good start.  Now, what code do we right to actually do the check?  Here’s where looking at the original method helps immensely.  We can check how many of an item we have using the item_number() method.  And we can set the number of a current item using the @items[] array.  Since we only want to do this for items (e.g. not weapons or armor) we have to check to be sure it’s actually an item.  Then, we also make sure it’s one of the special limited items.  Finally, we make sure there are no more than 5 of them currently held.  This gives us three lines of code to add in our placeholder above:

return unless item.is_a?(RPG::Item)
return unless LIMITED_ITEM_IDS.include?(item.id)
@items[item.id] = [number, 5].min

And, there we have it… the party will never hold more than 5 of any of those items.  Notice how, simply by knowing our event commands, it was very easy to figure out where to look to make our change.

2 comments… add one

  • Danny February 3, 2012, 6:33 am

    I’m greatful for the scripting tips and help, but don’t you think it’d be a good idea if Enterbrain partnered with someone to write a manual for rpg maker? It’s fine to tell someone to simply learn ruby script, but new users may have trouble making practical use of that info or applying it to scripting. I think it’d be much better if a book was made available for sale that not only thought Ruby, but was also geared towards showing how that knowledge applies to scripting but also why scripts and events do what they do.

    • Nick Palmer February 3, 2012, 7:53 am

      The thing is, a complete break down of RGSSx would take a huge manual. And even then it would have to be broken down further because of the differences between original, 2, and the upcoming 3. For syntax, you are just as well learning Ruby. For flow, you are better off studying basic program design (which is language neutral.)

      Once you learn the basics of scripting, which is what we are trying to cover, you can generally just learn the rest of RGSSx by looking at the already well documented base scripts. Also, if you really want to learn scripting, I would suggest you join some of the communities. There are lots of experienced scripters around to give great advice.

Leave a Comment