Intro to Scripting 3 with Tim

in Tutorials

Let’s revisit for a moment a topic that we discussed in our first tutorial, adjusting encounters so that they also happen while riding in the boat (or ship, or airship, for that matter). We saw how it was fairly easy to get this change in place, by only adjusting a few lines of code. But, we might notice there’s a slight problem… unless we craft dozens of different areas to perfectly match the contours of our water and land, we will end up with land dwellers attacking us while on the high seas, and sharks attacking us on land. Needless to say, this is kind of inadequate. Unfortunately, out of the box, there is no way to specify that troops only appear on certain types of tiles. So, we need to write a bit of script in order to make this work.

Before diving into how to do this, it’s worth digressing for just a brief moment to point out that a new feature in VX Ace, known as Regions, averts this issue. Though still impossible to define encounters based on tiles, rectangular areas have been replaced by regions, which can have any arbitrary shape by essentially painting them onto the map. So, the obvious solution would be to simply draw out separate land and water regions as needed, and assign appropriate troops to each. But until the English release, let’s continue looking at VX…

First step, we need to figure out an approach for how we want to write code to make this happen. One detail in particular: how do we determine what troops are allowed on land vs water? One answer would be to include in our code a list (array) of all troop ids denoted as either land or water. But, this is less than ideal, as it becomes very hard to maintain. A better solution is to somehow encode that information in the fields that are available from the Troops tab in the database. For a great many things, this is why noteboxes are so popular. (And by extension, why the noteboxes that exist in Ace for database tabs that previously did not have them, are so exciting.) Typically, scripters will write scripts that look in notebox content for special tags containing additional information. But, sadly, we don’t have a notebox here. So, the next best thing will be the Troop name. For my example, I’m going to assume that any name starting with “$L” is a land encounter, and “$W” represents a water encounter. Seems simple enough? Okay, let’s get to work then.

First, we need to find where in the code a troop is selected when a battle begins. In fact, in the very first tutorial, we stumbled across the method during our search for how to enable encounters while in the boat. But, supposing we don’t remember that too well, we could also think about possible searches that might help us find what we want. We might search for “encounter” or “troop”, for instance. An even better option, if we recall that both maps and areas have lists of troops, called “encounter lists”, is to see where in the code they are used, by searching for “encounter_list”. Sure enough, the method we need is in the Game_Player class (line numbers added for clarity):

 def make_encounter_troop_id
1   encounter_list = $game_map.encounter_list.clone
2   for area in $data_areas.values
3      encounter_list += area.encounter_list if in_area?(area)
4   end
5   if encounter_list.empty?
6      make_encounter_count
7      return 0
8   end
9   return encounter_list[rand(encounter_list.size)]

Hopefully, over time, we have been getting an increasingly better feel for how to read code, and digest it’s function. Nonetheless, for clarity, I will annotate the basics of what this method does:

  • Line 1 starts us out by copying the list of possible encounters on the current map.
  • Lines 2 through 4 iterate over all areas that have been setup. Checking each to see if we are currently within its area, if so then its encounter list is appended to our now growing list of possible encounters.
  • Line 5 checks if, after all that, we have an empty list (e.g. no possible encounter encounters where we currently stand). If so, lines 6-7 restart the encounter step counter, and bails out by returning a value of 0 as the selected troop id. (The rest of the game scripts interpret this as “nevermind, we’re not going to have an encounter after all”).
  • Lastly, line 9 picks a random value from our list.

Now, back to our goal, there are a few ways we could go about accomplishing what we want. One option would be to update the code on line 1 and line 3 in such a way that we only add the relevant options, rather than the full list. However, that implies we need to write essentially the same code in two different places. Or, perhaps we can wrap line 9 in a loop that checks if our selection is valid before returning it, and if not, makes a new selection? However, that approach might create a hard to track down error: suppose we were in the boat, on a tile which is not in an area with any water encounters? The loop would run indefinitely. The idea behind exploring a few options that do not work, is to make sure we stay aware of some general best practices in writing software (yes, we are actually writing software when we script!), and to be sure that what we do actually works in all cases.

Having ruled out those other choices, a good approach might be to add some new code between lines 4 and 5 that looks at our full list of possible encounters, and removes any that don’t meet our requirements. Note that this list is merely a list of numbers, the ID values of the troops. To get information about the troops themselves, we need to look at $data_troops. Specifically, $data_troops[x].name will give us the name of troop x. So, we want to setup a basic loop through encounter_list. We can do something like this:

i = 0
while i < encounter_list.size
   # do useful stuff here
   i += 1

Now we need to write our code to “do useful stuff”. In particular, look at the troop name, and if the designation in the name does not jive with our current travel type (boat or non boat), then remove that option from the list. Note that loops which iterate over an array are often written using array.each or array.each_item. In our specific case, however, the code inside the loop may remove elements from the array… which could cause some strange side effects. So, incrementing an index is a safer choice in our case.

Now, we want to handle the case for if we are riding in the boat, or if we are not. We can write some code like this, which acts based upon whether or not we are in the boat, or walking. In either case, it identifies if each troop is valid at the current time. If we have a valid troop, then we leave it alone, and move to the next possible troop in the list. If it’s invalid (we’re on land, and it’s a water troop, or vice versa), then we remove it from the list. Notice how, when we remove an item from our array, we don’t need to advance our index:

i = 0
while i < encounter_list.size
   valid_troop = false
   if @vehicle_type == 0       # in boat
      valid_troop = true if $data_troops[encounter_list[i]].name.start_with?("$W")
   elsif @vehicle_type == -1   # walking
      valid_troop = true if $data_troops[encounter_list[i]].name.start_with?("$L")
   if valid_troop
      i += 1

So, now that we’ve got that written, all we need to do to make it work, is to add a script which reopens the Game_Player class and redefines the make_encounter_troop_id method. As an exercise for the reader, consider how this could be extended to have different troops appear if the player is flying in an airship, or if they are walking through bush. There are probably even more possiblities not yet considered… if one were really ambitious, it’s possible to have different types of troops based on the TileID on which the player is standing, even.

2 comments… add one

Leave a Comment