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, @params, @params) $game_party.gain_item($data_items[@params], 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.