SLIP INTO RUBY - UNDER THE HOOD PART 10: IT HAS CHARACTER

A long-overdue breakdown of everything up to Game_Interpreter.

  • Trihan
  • 10/23/2015 04:08 PM
  • 6456 views
A belated welcome, fans of sport! It's been a while, but it's once again time to delve deep into the treacherous waters of RGSS3. That's right, it's time for another exciting edition of



Last time we looked at one of the largest default scripts in the program, Game_Map. Today, we're going to cover a LOT of ground as we race towards the finish line of the game objects classes. We're going to do eight scripts, which just leaves Game_Interpreter for next week to round us off before we start looking at sprites. Without further ado, let's get started!

Game_CommonEvent

Nice easy one to start, Game_CommonEvent handles common events, obviously, and includes functionality for parallel process events. It's used within the Game_Map class, and subsequently is part of $game_map.

def initialize(common_event_id)
    @event = $data_common_events[common_event_id]
    refresh
  end


Nice simple constructor. Initialize takes one argument: the ID of the common event you want to retrieve. The instance variable @event is set to the 'common_event_id' element of $data_common_events, which as we've previously looked at is a global variable containing all of the common events defined for the game. After this, we call the refresh method...

def refresh
    if active?
      @interpreter ||= Game_Interpreter.new
    else
      @interpreter = nil
    end
  end


...seen here. So if calling the active? method returns true (we'll see the code for this in a second) we return either @interpreter if one already exists, or Game_Interpreter.new, which creates a new one. Otherwise, @interpreter is set to nil.

In other words, if the common event is active, we create a new interpreter if we don't have one, or get the existing interpreter otherwise. If the common event isn't active, we don't have an interpreter for it.

def active?
    @event.parallel? && $game_switches[@event.switch_id]
  end


Here's where we determine whether an event is active or not. It's considered so IF the event in question is a parallel process AND the switch set as the activation switch for the event in the database is on.

def update
    if @interpreter
      @interpreter.setup(@event.list) unless @interpreter.running?
      @interpreter.update
    end
  end


Finally, we have the per-frame update method. If we currently have an interpreter set, we call its setup method supplying the list of event commands for the current event UNLESS the interpreter is already running, then we update the interpreter. This won't make a huge amount of sense until we actually look at Game_Interpreter, but it's not hard to fill in the blanks of what's happening.

Game_CharacterBase

Okay, now we're getting into the heavy hitters; this is another big one. As the name suggests, this is the base class that handles characters. It contains basic information about character events like their coordinates and graphics.

attr_reader   :id                       # ID
  attr_reader   :x                        # map X coordinate (logical)
  attr_reader   :y                        # map Y coordinate (logical)
  attr_reader   :real_x                   # map X coordinate (real)
  attr_reader   :real_y                   # map Y coordinate (real)
  attr_reader   :tile_id                  # tile ID (invalid if 0)
  attr_reader   :character_name           # character graphic filename
  attr_reader   :character_index          # character graphic index
  attr_reader   :move_speed               # movement speed
  attr_reader   :move_frequency           # movement frequency
  attr_reader   :walk_anime               # walking animation
  attr_reader   :step_anime               # stepping animation
  attr_reader   :direction_fix            # direction fix
  attr_reader   :opacity                  # opacity level
  attr_reader   :blend_type               # blending method
  attr_reader   :direction                # direction
  attr_reader   :pattern                  # pattern
  attr_reader   :priority_type            # priority type
  attr_reader   :through                  # pass-through
  attr_reader   :bush_depth               # bush depth
  attr_accessor :animation_id             # animation ID
  attr_accessor :balloon_id               # balloon icon ID
  attr_accessor :transparent              # transparency flag


We've got a metric buttload of public instance variables for this one, most of which should hopefully be self-explanatory from their name and comment. Most are attr_readers, meaning they can only be retrieved and not set, with the exception of animation ID, balloon icon ID, and transparency flag.

def initialize
    init_public_members
    init_private_members
  end


The constructor is just a means to calling two other methods, one to initialise public member variables and one to initialise the private ones.

def init_public_members
    @id = 0
    @x = 0
    @y = 0
    @real_x = 0
    @real_y = 0
    @tile_id = 0
    @character_name = ""
    @character_index = 0
    @move_speed = 4
    @move_frequency = 6
    @walk_anime = true
    @step_anime = false
    @direction_fix = false
    @opacity = 255
    @blend_type = 0
    @direction = 2
    @pattern = 1
    @priority_type = 1
    @through = false
    @bush_depth = 0
    @animation_id = 0
    @balloon_id = 0
    @transparent = false
  end


Initialising the public member variables is a fairly simple matter of setting all of the above attr_reader and attr_accessors to their default values. Notable defaults are 4 for move speed, 6 for move frequency, 255 for opacity, and 2 (down) for direction. There are a couple of variables that may not be immediately obviously in their application but we should hopefully address that in a second.

def init_private_members
    @original_direction = 2               # Original direction
    @original_pattern = 1                 # Original pattern
    @anime_count = 0                      # Animation count
    @stop_count = 0                       # Stop count
    @jump_count = 0                       # Jump count
    @jump_peak = 0                        # Jump peak count
    @locked = false                       # Locked flag
    @prelock_direction = 0                # Direction before lock
    @move_succeed = true                  # Move success flag
  end


What's this? Character events have HIDDEN VARIABLES what on earth Trihan how could Enterbrain betray us like this oh my god

Don't panic! It's not as bad as it looks. Okay, so we've got original direction, which is...well, the character's original direction. You'll soon see what this is for. Original pattern is the character's original charset index. Anime count, stop count, jump count and jump peak are counters used for various types of movement, as we'll also see soon. Locked is a flag determining whether a character is locked in place, prelock_direction is a flag determining whether the direction is locked, and move_succeed is a flag telling Ruby whether a character movement was successful or not.

def pos?(x, y)
    @x == x && @y == y
  end


This method returns whether a given X and Y coordinate is equal to that of the character by comparing the supplied x parameter to the character's @x variable, AND the supplied y parameter to the character's @y variable. The method will return true if both are a match, false otherwise.

def pos_nt?(x, y)
    pos?(x, y) && !@through
  end


This one is similar but includes a check for whether "pass through" is currently OFF; in other words, the event isn't capable of freely passing through obstacles.

def normal_priority?
    @priority_type == 1
  end


Behind the scenes, the default scripts assign integer values to the indexes of things you select in drop-down menus in the editor. For example, when you pick the priority type for an event, "below hero" is 0, "same level as hero" is 1, and "above hero" is 2. You'll note that by default @priority_type was set to 1, so all new events are at the same level as the hero unless specified otherwise. This method checks whether the character is at the same level as the hero by checking whether its @priority_type is 1. Obviously if this is the case the method returns true, and if not it'll return false.

def moving?
    @real_x != @x || @real_y != @y
  end


This method checks whether the character is moving; if its @real_x OR @real_y instance variables differ from its @x and @y, return true, otherwise return false. Think of @x and @y as the actual coordinate occupied by the character, while @real_x and @real_y are where it should be (which will be different from its actual coordinate if it's currently moving).

def jumping?
    @jump_count > 0
  end


This method checks whether the character is jumping by seeing if its @jump_count is greater than 0: if so, return true. Otherwise return false.

def jump_height
    (@jump_peak * @jump_peak - (@jump_count - @jump_peak).abs ** 2) / 2
  end


This method calculates jump height: the equation won't make a huge amount of sense right now because most of the variables involves have been initialised at 0, but when we get to Game_Character the full impact will become clear. For now, suffice to say that this method returns half of (@jump_peak squared) minus (the absolute value of (@jump_count minus @jump_peak) squared).

def stopping?
    !moving? && !jumping?
  end


Little bit of obvious boolean logic to tell whether the character is stopping. Returns true IF the character is NOT moving AND NOT jumping. Couldn't be simpler.

def real_move_speed
    @move_speed + (dash? ? 1 : 0)
  end


This method gets the character's real move speed. Returns @move_speed (which is 4 by default, remember) + 1 if dashing (so 5).

def distance_per_frame
    2 ** real_move_speed / 256.0
  end


This method calculates the character's actual move distance per frame, in pixels. Returns the square of real_move_speed divided by 256.

def dash?
    return false
  end


This method determines whether the character is dashing. The default for the base class always returns false; this will be overwritten for the player class, which is actually capable of dashing.

def debug_through?
    return false
  end


This method determines whether the debugging pass-through state is active (that thing where you can walk through anything during test play by holding down CTRL). In the base class, it always returns false.

def straighten
    @pattern = 1 if @walk_anime || @step_anime
    @anime_count = 0
  end


This method literally straightens out the animation. Simply enough it sets the animation pattern to 1 if walk animation or step animation are set, and sets the animation count to 0. We'll see what these variables are used for shortly.

def reverse_dir(d)
    return 10 - d
  end


Another method that does exactly what it says on the tin, this one reverses the character's direction by returning its current direction subtracted from 10. Doing the math here, that means that up (8) is converted to 2 (10 - 8); down (2) is converted to 8 (10 - 2); left (4) is converted to 6 (10 - 4) and right (6) is converted to 4 (10 - 6). Not exactly rocket science.

def passable?(x, y, d)
    x2 = $game_map.round_x_with_direction(x, d)
    y2 = $game_map.round_y_with_direction(y, d)
    return false unless $game_map.valid?(x2, y2)
    return true if @through || debug_through?
    return false unless map_passable?(x, y, d)
    return false unless map_passable?(x2, y2, reverse_dir(d))
    return false if collide_with_characters?(x2, y2)
    return true
  end


Okay, this one's slightly more complex. It determines whether an X or Y tile is traversible to the tile one space from it in a given direction.

First, a temporary set of X and Y are set to the result of calling the round_x/y_with_direction from $game_map, which we already looked at eons ago. For anyone who needs a reminder they basically calculate a coordinate shifted one tile in the supplied direction adjusted for looping.

We return false if the tile isn't a valid coordinate on the map, again courtesy of $game_map. It stands to reason that if the destination tile isn't on the map, the check has already failed as we can't move into it.

We return true if the through attribute is set, or if the debug through is active. Obviously if the user has specifically set stuff to phase, or is test playing and passing through things, we'll let them go pretty much anywhere.

We return false unless the passability setting of the current tile allows movement in the supplied direction. If you're trying to move down on a tile that doesn't allow downward movement, failure again stands to reason.

We return false unless the passability of the destination tile allows movement in the opposite of the travel direction. Again, we won't allow downward movement to a tile that wouldn't have allowed upward movement.

Penultimately, we return false if there are any normal priority events or non-airship vehicles on the destination tile.

Finally, having exhausted every possibility of combinations of movement checks, we have no other choice but to return true, allowing the instance of the class to finally move to its desired tile.

def diagonal_passable?(x, y, horz, vert)
    x2 = $game_map.round_x_with_direction(x, horz)
    y2 = $game_map.round_y_with_direction(y, vert)
    (passable?(x, y, vert) && passable?(x, y2, horz)) ||
    (passable?(x, y, horz) && passable?(x2, y, vert))
  end


Oddly enough, this is exactly the same thing only checking passability while moving diagonally. The horz parameter denotes horizontal movement and the vert denotes vertical. So left/up would be 4/8, right/down would be 6/2 etc.

def map_passable?(x, y, d)
    $game_map.passable?(x, y, d)
  end


This method checks whether the character can travel to a given tile on the map in a given direction, by passing the same parameters to the passable? method of $game_map.

def collide_with_characters?(x, y)
    collide_with_events?(x, y) || collide_with_vehicles?(x, y)
  end


This method checks for a collision with characters at given coordinates, and simply returns the result of collide_with_events? and collide_with_vehicles? at the same coordinates. We're literally just about to look at those methods.

def collide_with_events?(x, y)
    $game_map.events_xy_nt(x, y).any? do |event|
      event.normal_priority? || self.is_a?(Game_Event)
    end
  end


Okay, so colliding with events. As you may not remember from our foray into Game_Map, the events_xy_nt method gets an array of events at the given coordinates EXCLUDING PASSTHROUGH. So this is basically saying "return true if any event from the array of all events at the supplied X and Y have normal priority or are an instance of Game_Event". (the latter check is for things like signposts and doors, which obviously aren't characters).

def collide_with_vehicles?(x, y)
    $game_map.boat.pos_nt?(x, y) || $game_map.ship.pos_nt?(x, y)
  end


This is the counterpart method to collide_with_events?, and checks for the presence of a boat or ship at the given coordinates. As the boat and ship properties of $game_map are instances of Game_Vehicle, which are in turn instances of Game_Character, which are in turn instances of the class we're currently looking at, they, like all other Game_CharacterBase instances, have the pos_nt? method which checks their coordinate and whether they have "through" on.

def moveto(x, y)
    @x = x % $game_map.width
    @y = y % $game_map.height
    @real_x = @x
    @real_y = @y
    @prelock_direction = 0
    straighten
    update_bush_depth
  end


This is the method where the magic of movement happens for characters. There are a few things happening here.

The instance's x variable is set to the remainder of the passed-in x divided by the map's width, and the same for the y and height. (this is to allow for looping, obviously). The real_x and real_y are set to the same values as the newly-calculated x and y. prelock_direction is set to 0, which doesn't really seem to do an awful lot (it's only used in one place that I can see), then the straighten method we looked at above is called, and finally update_bush_depth is called, which does a bunch more stuff than it probably should for the privilege of it briefly looking like bushes are partially obscuring the character. We'll be looking at that soon.

def set_direction(d)
    @direction = d if !@direction_fix && d != 0
    @stop_count = 0
  end


Back to the simple stuff now, this method merely sets the character's direction. It sets the direction variable to the passed-in value but only if direction fix is off and the direction passed in isn't 0. It also sets stop_count to 0, which is a counter for the number of frames to wait before automatic movement.

def tile?
    @tile_id > 0 && @priority_type == 0
  end


This method checks whether an event is a tile (which I think is basically set automatically by the editor for events whose graphics are taken from the tileset) by checking that their tile ID is greater than 0, and their priority type is 0 (below characters). The rationale being, I guess, that only stuff that appears underneath normal characters could really be logically considered a tile.

def object_character?
    @tile_id > 0 || @character_name[0, 1] == '!'
  end


This method returns whether the character is actually an object. It returns true if the tile ID is greater than 0 or the first character of its graphic filename is an exclamation mark.

def shift_y
    object_character? ? 0 : 4
  end


...and all it actually does (besides being a check for bush depth, as we'll see) is determine that the number of pixels to shift up from the tile position is 0 for an object, and 4 for a character. This is why characters appear slightly higher.

def screen_x
    $game_map.adjust_x(@real_x) * 32 + 16
  end


This method calculates the character's actual screen coordinate in pixels; it's basically the adjusted X coordinate multipled by 32, plus 16.

def screen_y
    $game_map.adjust_y(@real_y) * 32 + 32 - shift_y - jump_height
  end


And more or less the same thing for Y. As you can see it's subtracting shift_y, so characters will be 4 pixels higher up.

def screen_z
    @priority_type * 100
  end


Screen Z is determined as 100 multiplied by the character's priority type; below hero will be 0, same level will be 100, and above hero will be 200.

def update
    update_animation
    return update_jump if jumping?
    return update_move if moving?
    return update_stop
  end


The update method updates the character's animation, updates jump if they're jumping, updates move if they're moving, and finally updates stop. We'll see these methods in detail shortly.

def update_jump
    @jump_count -= 1
    @real_x = (@real_x * @jump_count + @x) / (@jump_count + 1.0)
    @real_y = (@real_y * @jump_count + @y) / (@jump_count + 1.0)
    update_bush_depth
    if @jump_count == 0
      @real_x = @x = $game_map.round_x(@x)
      @real_y = @y = $game_map.round_y(@y)
    end
  end


Okay, so updating a jump. First we decrement the jump counter by 1. Then, we calculate the real X and Y coordinate as the results of multiplying the real values by jump count and adding the X and Y, then dividing that by the jump count + 1. We haven't looked at the actual jump initiation methods yet, so it doesn't make much sense to delve too deeply into this. When we get to that part of Game_Character I'll explain how this all works.

After that, we update the bush depth. If the jump count is 0, we set the real X and Y to X and Y which are set to the round_x/y methods of $game_map. I can't remember whether I've explained this kind of assignment before, but basically as assignment in Ruby is right-to-left, you can assign multiple variables to the result of whatever's on the far right. For example, if I had four different values I wanted to set to 1, I could do

value1 = value2 = value3 = value4 = 1

They're evaluated right to left, so first value4 is set to 1, then value3 is set to value4, then value2 is set to value3, and finally value1 is set to value2.

def update_move
    @real_x = [@real_x - distance_per_frame, @x].max if @x < @real_x
    @real_x = [@real_x + distance_per_frame, @x].min if @x > @real_x
    @real_y = [@real_y - distance_per_frame, @y].max if @y < @real_y
    @real_y = [@real_y + distance_per_frame, @y].min if @y > @real_y
    update_bush_depth unless moving?
  end


Again, I'll probably come back to the explanation here once we get to Game_Character, but I can give a brief summary.

If x/y are less than real_x/y, the real values are set to the maximum value between the real minus distance per frame (in pixels), or x/y. If the x/y are greater than real_x/y, they're set to the minimum value from that check. Finally, unless the character is moving bush depth is updated.

def update_stop
    @stop_count += 1 unless @locked
  end


Pretty simple method, this. Just adds 1 to stop count unless the character is locked (this flag is basically just to track when events are locked down by the player interacting with them, as we'll see later).

def update_animation
    update_anime_count
    if @anime_count > 18 - real_move_speed * 2
      update_anime_pattern
      @anime_count = 0
    end
  end


This method updates the animation. First, update_anime_count is called, a method we'll see after this one. If the anime count is greater than 18 minus double the real move speed, we update the anime pattern and set the count to 0. For a normal character event, as you'll remember from before I'm sure, real_move_speed is basically just whatever move speed is set in the editor. So by default, this condition will be true if anime count is greater than 18 - 6, which is obviously 12.

def update_anime_count
    if moving? && @walk_anime
      @anime_count += 1.5
    elsif @step_anime || @pattern != @original_pattern
      @anime_count += 1
    end
  end


Here's the next third of the coin. Okay, so if the character is moving and walk animation is enabled anime_count is increased by 1.5. Otherwise, if step animation is enabled or the current pattern isn't the same as the original, increase the count by 1.

def update_anime_pattern
    if !@step_anime && @stop_count > 0
      @pattern = @original_pattern
    else
      @pattern = (@pattern + 1) % 4
    end
  end


And to round things off, here's the anime pattern update method. So if the step animation is -not- enabled and the stop count is greater than 0, the character's pattern is set to its original (standing still, basically). Otherwise, the pattern is set to the next pattern along mod 4, which obviously means when it gets past pattern 4 it'll loop around to the beginning.

Let's take the player moving around as our example here, with an initial count of 0. update_anime_count is called; the player is moving and walk animation is enabled, so anime count is now 1.5. The condition is checking to see if that value is > 12, which it isn't. This process continues for a count of 3, 4.5, 6, 7.5, 9, 10.5, 12, 13.5, at which point update anime pattern is called. Step animation isn't enabled for the player, so we go to their charset's next movement pattern. Note that this process would have taken 8 frames, or about 1/7 of a second. Anime count is reset to 0, and the process repeats. This is how characters animate, basically.

def ladder?
    $game_map.ladder?(@x, @y)
  end


One of the simpler methods, this one just checks whether the current tile is a ladder by calling $game_map's ladder? method.

def update_bush_depth
    if normal_priority? && !object_character? && bush? && !jumping?
      @bush_depth = 8 unless moving?
    else
      @bush_depth = 0
    end
  end


We've seen this one called a few times, so now it's finally time to look at the infamous bush depth update. If the character is normal priority (same level as hero) AND not an object (by process of elimination making them a character) AND the current tile is a bush (as we'll see in a second) AND the character isn't jumping, bush depth is set to 8 unless the character is currently moving. Otherwise, bush depth is set to 0.

This is how the partial transparency when standing still in bushes effect is achieved.

def bush?
    $game_map.bush?(@x, @y)
  end


Pretty much the same as the ladder? method, this checks whether the character's current tile is a bush by calling the bush? method of $game_map.

def terrain_tag
    $game_map.terrain_tag(@x, @y)
  end


Similarly, this one returns the current tile's terrain tag...

def region_id
    $game_map.region_id(@x, @y)
  end


...and this one, the region ID.

def increase_steps
    set_direction(8) if ladder?
    @stop_count = 0
    update_bush_depth
  end


This method is called when a character's number of steps is increased. Their direction is set to up if they're on a ladder, stop count is set to 0, and bush depth is updated.

def set_graphic(character_name, character_index)
    @tile_id = 0
    @character_name = character_name
    @character_index = character_index
    @original_pattern = 1
  end


This method changes the graphics for a character. First, their tile ID is set to 0, then the name of their charset and index are changed to the passed-in values, and finally the character's original pattern is set to 1 (the middle one of the animation).

def check_event_trigger_touch_front
    x2 = $game_map.round_x_with_direction(@x, @direction)
    y2 = $game_map.round_y_with_direction(@y, @direction)
    check_event_trigger_touch(x2, y2)
  end


This method determines whether a frontal touch event has been triggered. This basically just gets the adjusted coordinates of the tile being moved to and calls another method, check_event_trigger_touch, on that tile.

def check_event_trigger_touch(x, y)
    return false
  end


And this is what it does. Checking for touch event triggers isn't really relevant for normal base character instances; the player class will overwrite this, as we'll see.

def move_straight(d, turn_ok = true)
    @move_succeed = passable?(@x, @y, d)
    if @move_succeed
      set_direction(d)
      @x = $game_map.round_x_with_direction(@x, d)
      @y = $game_map.round_y_with_direction(@y, d)
      @real_x = $game_map.x_with_direction(@x, reverse_dir(d))
      @real_y = $game_map.y_with_direction(@y, reverse_dir(d))
      increase_steps
    elsif turn_ok
      set_direction(d)
      check_event_trigger_touch_front
    end
  end


This method moves the character one tile in a given direction, with an optional boolean parameter called turn_ok which defaults to true.

The internal movement success flag is set to the result of whether the character is able to pass from their current tile to the next one along in the direction of travel. If the flag is now true, the character's direction is set to the one they're moving in, their x, y, real_x and real_y coordinates are updated, and steps are increased. If the move wasn't successful and turn_ok is true, the character's direction is still set to the direction of travel and we check to see whether any frontal touch events were hit.

def move_diagonal(horz, vert)
    @move_succeed = diagonal_passable?(x, y, horz, vert)
    if @move_succeed
      @x = $game_map.round_x_with_direction(@x, horz)
      @y = $game_map.round_y_with_direction(@y, vert)
      @real_x = $game_map.x_with_direction(@x, reverse_dir(horz))
      @real_y = $game_map.y_with_direction(@y, reverse_dir(vert))
      increase_steps
    end
    set_direction(horz) if @direction == reverse_dir(horz)
    set_direction(vert) if @direction == reverse_dir(vert)
  end


This method is for moving a character diagonally. The initial success is determined by whether the character can move diagonally in the horizontal and vertical directions passed in. If this is successful, everything is pretty much the same as the previous method only without the else. and with a couple more checks on set_direction at the end.

And that's it for the base character class!

Game_Character
This child class of Game_CharacterBase is just as long and complex as its parent, and mainly contains code related to movement.

I won't bother summarising the constants as they're pretty much entirely self-explanatory, but suffice to say that every option available to you in a "Set Move Route..." command has a textual constant equivalent that maps to its internal integer representation. For example, internally "Turn 180 degrees" is represented by the number 22, but in your code you can just call it ROUTE_TURN_180D.

There's only one public instance variable; move_route_forcing, which is a flag determining whether the character's move route is being forced upon it. This will be the case anywhere a character's movement is specifically set using event commands.

def init_public_members
    super
    @move_route_forcing = false
  end


Nice boring constructor. Calls the init_public_members of its parent (Game_CharacterBase) and sets the move route force flag to false.

def init_private_members
    super
    @move_route = nil                 # Move route
    @move_route_index = 0             # Move route execution position
    @original_move_route = nil        # Original move route
    @original_move_route_index = 0    # Original move route execution position
    @wait_count = 0                   # Wait count
  end


Initialing private members is similar. First, we call the parent class's version, then set (original_)move_route to nil, (original_)move_route_index to 0, and finally wait count to 0.

def memorize_move_route
    @original_move_route        = @move_route
    @original_move_route_index  = @move_route_index
  end


This method is used to memorise the move route and is called as the character begins a forced movement. Simply sets the original move route to the current move route, and the original move route index to the current index.

def restore_move_route
    @move_route           = @original_move_route
    @move_route_index     = @original_move_route_index
    @original_move_route  = nil
  end


This method restores the character's previous move route, basically the opposite of the previous method.

def force_move_route(move_route)
    memorize_move_route unless @original_move_route
    @move_route = move_route
    @move_route_index = 0
    @move_route_forcing = true
    @prelock_direction = 0
    @wait_count = 0
  end


This is the method called when a move route is being forced. First, the original route is memorised. Then, the current move route is set to the passed-in parameter, the index set to 0, the move_route_forcing flag is set to true, prelock direction is set to 0, and wait count is set to 0.

def update_stop
    super
    update_routine_move if @move_route_forcing
  end


This method updates the character movement while stopped; it simply calls the same method of its superclass and then updates the move routine if being forced to move.

def update_routine_move
    if @wait_count > 0
      @wait_count -= 1
    else
      @move_succeed = true
      command = @move_route.list[@move_route_index]
      if command
        process_move_command(command)
        advance_move_route_index
      end
    end
  end


This method updates the move routine: if the wait count is greater than 0, decrement it by 1. Otherwise, set the move success flag to true, and set the command to the element of the current move route's movement list at the current index. If there's a command present to be processed, call the process_move_command method passing in the current command, then call advance_move_route_index. These are both methods we're just about to look at.

def process_move_command(command)
    params = command.parameters
    case command.code
    when ROUTE_END;               process_route_end
    when ROUTE_MOVE_DOWN;         move_straight(2)
    when ROUTE_MOVE_LEFT;         move_straight(4)
    when ROUTE_MOVE_RIGHT;        move_straight(6)
    when ROUTE_MOVE_UP;           move_straight(8)
    when ROUTE_MOVE_LOWER_L;      move_diagonal(4, 2)
    when ROUTE_MOVE_LOWER_R;      move_diagonal(6, 2)
    when ROUTE_MOVE_UPPER_L;      move_diagonal(4, 8)
    when ROUTE_MOVE_UPPER_R;      move_diagonal(6, 8)
    when ROUTE_MOVE_RANDOM;       move_random
    when ROUTE_MOVE_TOWARD;       move_toward_player
    when ROUTE_MOVE_AWAY;         move_away_from_player
    when ROUTE_MOVE_FORWARD;      move_forward
    when ROUTE_MOVE_BACKWARD;     move_backward
    when ROUTE_JUMP;              jump(params[0], params[1])
    when ROUTE_WAIT;              @wait_count = params[0] - 1
    when ROUTE_TURN_DOWN;         set_direction(2)
    when ROUTE_TURN_LEFT;         set_direction(4)
    when ROUTE_TURN_RIGHT;        set_direction(6)
    when ROUTE_TURN_UP;           set_direction(8)
    when ROUTE_TURN_90D_R;        turn_right_90
    when ROUTE_TURN_90D_L;        turn_left_90
    when ROUTE_TURN_180D;         turn_180
    when ROUTE_TURN_90D_R_L;      turn_right_or_left_90
    when ROUTE_TURN_RANDOM;       turn_random
    when ROUTE_TURN_TOWARD;       turn_toward_player
    when ROUTE_TURN_AWAY;         turn_away_from_player
    when ROUTE_SWITCH_ON;         $game_switches[params[0]] = true
    when ROUTE_SWITCH_OFF;        $game_switches[params[0]] = false
    when ROUTE_CHANGE_SPEED;      @move_speed = params[0]
    when ROUTE_CHANGE_FREQ;       @move_frequency = params[0]
    when ROUTE_WALK_ANIME_ON;     @walk_anime = true
    when ROUTE_WALK_ANIME_OFF;    @walk_anime = false
    when ROUTE_STEP_ANIME_ON;     @step_anime = true
    when ROUTE_STEP_ANIME_OFF;    @step_anime = false
    when ROUTE_DIR_FIX_ON;        @direction_fix = true
    when ROUTE_DIR_FIX_OFF;       @direction_fix = false
    when ROUTE_THROUGH_ON;        @through = true
    when ROUTE_THROUGH_OFF;       @through = false
    when ROUTE_TRANSPARENT_ON;    @transparent = true
    when ROUTE_TRANSPARENT_OFF;   @transparent = false
    when ROUTE_CHANGE_GRAPHIC;    set_graphic(params[0], params[1])
    when ROUTE_CHANGE_OPACITY;    @opacity = params[0]
    when ROUTE_CHANGE_BLENDING;   @blend_type = params[0]
    when ROUTE_PLAY_SE;           params[0].play
    when ROUTE_SCRIPT;            eval(params[0])
    end
  end


WHOA. This is a long method, but it's really quite simple. First of all, the params for the movement are set to command.parameters (basically whatever was set when the user created the movement event command) and then there's a massive case statement to deal with each corresponding button from the event commands UI. The only one that doesn't correspond to a button is ROUTE_END, which denotes the end of the movement list and calls process_route_end.

def distance_x_from(x)
    result = @x - x
    if $game_map.loop_horizontal? && result.abs > $game_map.width / 2
      if result < 0
        result += $game_map.width
      else
        result -= $game_map.width
      end
    end
    result
  end


This method calculates how far a given X coordinate is from the character's current X. Result is initially set to current X minus the passed-in value. If the map is looping horizontally and the absolute value of result (the actual distance in tiles either direction) is greater than half the map's width (in other words, they're closer to the destination in the opposite direction as the map loops), there's an additional check: if result < 0, in other words the character is to the destination's left, we add the map's width to the result. Otherwise, we subtract the width from result.

Let's take an example: say we have a horizontally-looping 17x13 map, a character who's at (2,12) and the destination X is 16. Result will initially be 2 - 16, or -14. The if statement will check horizontal looping (true) AND 14 > 17 / 2, which is also true. Result was negative, so we add the map's width to result. This gives us a final result of -14 + 17 = 3, meaning the destination is actually 3 tiles away.

def distance_y_from(y)
    result = @y - y
    if $game_map.loop_vertical? && result.abs > $game_map.height / 2
      if result < 0
        result += $game_map.height
      else
        result -= $game_map.height
      end
    end
    result
  end


Literally exactly the same thing but for Y.

def move_random
    move_straight(2 + rand(4) * 2, false)
  end


This method makes the character move one tile in a random direction, preventing turns. 2 + rand(4) * 2 either gives a value of 2, 4, 6 or 8, corresponding to the directions of movement.

def move_toward_character(character)
    sx = distance_x_from(character.x)
    sy = distance_y_from(character.y)
    if sx.abs > sy.abs
      move_straight(sx > 0 ? 4 : 6)
      move_straight(sy > 0 ? 8 : 2) if !@move_succeed && sy != 0
    elsif sy != 0
      move_straight(sy > 0 ? 8 : 2)
      move_straight(sx > 0 ? 4 : 6) if !@move_succeed && sx != 0
    end
  end


This is where the distance methods come in; moving this character towards another one. sx is set to the distance from the destination character's X, and likewise with sy/Y. If the absolute X distance is greater than the absolute Y distance (that is to say they're further horizontally than vertically) the character moves one tile to the left if the X distance is positive, to the right otherwise. Then if that move didn't succeed and the Y difference isn't 0, the characters moves one tile up if the Y distance is positive, and down otherwise. If the absolute X is NOT greater, there's another check: Y distance not being 0. If this is true, we do the same process again but in the opposite order: first we try to move up/down, and if we can't we try to move left/right.

Let's say our current character is at (17,35) and the destination character is at (5,12), on a non-looping map, assuming no obstacles. The X distance is 12, and Y distance is 23. The X distance is NOT greater than the Y, and Y distance is not 0. It's positive, so the character moves straight down to (17, 34). This will continue until the character gets to (17,21), at which point the X distance is now greater than the Y. It's positive, so they move straight left to (16,21) then again to (15, 21). From here they'll move in a staircase pattern until they get to the character they're moving towards.

def move_away_from_character(character)
    sx = distance_x_from(character.x)
    sy = distance_y_from(character.y)
    if sx.abs > sy.abs
      move_straight(sx > 0 ? 6 : 4)
      move_straight(sy > 0 ? 2 : 8) if !@move_succeed && sy != 0
    elsif sy != 0
      move_straight(sy > 0 ? 2 : 8)
      move_straight(sx > 0 ? 6 : 4) if !@move_succeed && sx != 0
    end
  end


Pretty much exactly the same thing only for moving away from a character instead of towards it.

def turn_toward_character(character)
    sx = distance_x_from(character.x)
    sy = distance_y_from(character.y)
    if sx.abs > sy.abs
      set_direction(sx > 0 ? 4 : 6)
    elsif sy != 0
      set_direction(sy > 0 ? 8 : 2)
    end
  end


This method turns a character to face another one. It follows similar logic to the movement: if the X distance is greater than the Y distance, they'll turn to face left if the difference is positive, or right if it's negative. Otherwise, if the Y distance isn't 0 they'll turn to face up if the difference is positive and down if it's negative.

def turn_away_from_character(character)
    sx = distance_x_from(character.x)
    sy = distance_y_from(character.y)
    if sx.abs > sy.abs
      set_direction(sx > 0 ? 6 : 4)
    elsif sy != 0
      set_direction(sy > 0 ? 2 : 8)
    end
  end


Literally the exact opposite of the previous method.

def turn_toward_player
    turn_toward_character($game_player)
  end


This is the method used when you select the movement command "Turn toward player". And yes, that does mean what you think it means: you can use a script to call the move/turn_toward/away_from_character methods and pass in any event you like, and it'll work the same way. Interestingly enough followers (your caterpillar party) don't use this method, as we'll see when we get to Game_Follower.

def move_away_from_player
    move_away_from_character($game_player)
  end


Second verse, same as the first.

def move_forward
    move_straight(@direction)
  end


Moves straight forward 1 tile in the character's current direction.

def move_backward
    last_direction_fix = @direction_fix
    @direction_fix = true
    move_straight(reverse_dir(@direction), false)
    @direction_fix = last_direction_fix
  end


Moves straight backward 1 tile in the opposite of the character's current direction. Fixes direction first so they are literally stepping backwards.

def jump(x_plus, y_plus)
    if x_plus.abs > y_plus.abs
      set_direction(x_plus < 0 ? 4 : 6) if x_plus != 0
    else
      set_direction(y_plus < 0 ? 8 : 2) if y_plus != 0
    end
    @x += x_plus
    @y += y_plus
    distance = Math.sqrt(x_plus * x_plus + y_plus * y_plus).round
    @jump_peak = 10 + distance - @move_speed
    @jump_count = @jump_peak * 2
    @stop_count = 0
    straighten
  end


This method processes a jump, taking in values for how far on the X and Y axes the jump will take the character.

If the absolute value of the x_plus is greater than the absolute value of the y_plus, we set the character's direction to left (if the distance is negative) or right (if positive) if x_plus is not equal to 0. If the absolute X is NOT greater, we set the character's direction to up (if the y plus is negative) or down (if positive) if y_plus is not equal to 0.

After this, we add x_plus and y_plus to the character's current coordinates.

The distance is calculated as the square root of the squares of x_plus and y_plus rounded. Jump peak is calculated as 10 + the distance - the character's move speed. Jump count is calculated as jump peak * 2. Stop count is set to 0, and finally we call straighten.

Now that we've seen this, I can go through an example and incorporate it into update_jump.

So let's say we have a character at (12, 8) moving normal speed and we want him to jump 2 tiles left and 2 tiles down, to (10, 10). The call to jump will look like this:

jump(-2, 2)


The absolute x_plus isn't greater than y, because they're the same, so we go to the else part of the if statement.

y_plus is not equal to 0, and is positive, so we set the character's direction to down. -2 is added to @x, making it 10, and 2 is added to @y, making it 10 as well.

Distance is the square root of ((-2 * -2) + (2 * 2)), which simplifies to 4 + 4, which is 8. The square root of 8 is like 2.88427... which rounds to 3.

Jump peak is 10 + 3 - 3, or 10. Jump count is therefore 20. Stop count is initialised at 0, and straighten just resets the character graphic.

Now that everything is set up, CharacterBase's update_jump does the rest:

@jump_count is decremented, and is now 19. @real_x is set to (12 * 19 + 10) / (19 + 1.0) which is 11.9. @real_y is set to (8 * 19 + 10) / (19 + 1.0) which is 8.1.
The next time it's decremented, it's now 18. @real_x is set to (11.9 * 18 + 10) / (18 + 1.0) which is 11.8. @real_y is set to (8.1 * 18 + 10) / (18 + 1.0) which is 8.2.
Then it's (11.8 * 17 + 10) / (17 + 1.0) which is 11.7, and (8.2 * 17 + 10) / (17 + 1.0) which is 8.3...and so on and so forth until both @real_x and @real_y are 10. You get the idea.

def process_route_end
    if @move_route.repeat
      @move_route_index = -1
    elsif @move_route_forcing
      @move_route_forcing = false
      restore_move_route
    end
  end


Oddly enough, this one fires when the end of a route is being processed. I know! Crazy, right?

So if the move route is set to repeat, the index is set to -1. Due to the fact that Ruby supports negative indexing in arrays, this logically means that next time a move update happens, it will process the last command in the list (which is the end of the movement); the index will be incremented to 0 and the movement will start anew.

def advance_move_route_index
    @move_route_index += 1 if @move_succeed || @move_route.skippable
  end


This method simply increments the move route index if the move was successful or the route was set to "skip if cannot move".

def turn_right_90
    case @direction
    when 2;  set_direction(4)
    when 4;  set_direction(8)
    when 6;  set_direction(2)
    when 8;  set_direction(6)
    end
  end


This method turns a character to the right 90 degrees; simply checks which direction they're currently facing and then changes it to whichever one would result from a 90 degree right turn.

def turn_left_90
    case @direction
    when 2;  set_direction(6)
    when 4;  set_direction(2)
    when 6;  set_direction(8)
    when 8;  set_direction(4)
    end
  end


Exactly the same but for a left turn.

def turn_180
    set_direction(reverse_dir(@direction))
  end


Turns a character 180 degrees by changing their direction to its reverse.

def turn_right_or_left_90
    case rand(2)
    when 0;  turn_right_90
    when 1;  turn_left_90
    end
  end


Turns a character 90 degrees randomly right or left. I don't think I need to explain this.

def turn_random
    set_direction(2 + rand(4) * 2)
  end


Turn to face a completely random direction.

def swap(character)
    new_x = character.x
    new_y = character.y
    character.moveto(x, y)
    moveto(new_x, new_y)
  end


Swaps the positions of two characters around. Basically sets the X and Y position of the target to that of the current character, and the current character's X and Y to that of the target.

And that's it for Game_Character!

Game_Player
This is the class that handles the actual player character, and is referenced by $game_player. The class inherits from Game_Character, which makes it clear that for all intents and purposes there's no difference between the character you control and the ones dotted around the map. Anything they can do you can do, but player obviously has a few extras.

attr_reader   :followers                # Followers (party members)


This is a list of party members who will follow behind the player. Note that as it's an attr_reader it cannot be assigned to directly.

def initialize
    super
    @vehicle_type = :walk           # Type of vehicle currently being ridden
    @vehicle_getting_on = false     # Boarding vehicle flag
    @vehicle_getting_off = false    # Getting off vehicle flag
    @followers = Game_Followers.new(self)
    @transparent = $data_system.opt_transparent
    clear_transfer_info
  end


This is the constructor for the player. It first calls the initialize method of its base class. Then it sets a number of instance variables: @vehicle_type, which is set to the symbol :walk (this denotes the kind of vehicle the player is riding, and obviously when the player is first created they're on foot), the @vehicle_getting_on flag, which is false, the @vehicle_getting_off flag, which is false, @followers, which is a new instance of Game_Followers passing in the player object, @transparent, which is an option you set in the system tab of the database (this basically determines whether you can see the player character graphic when the game starts, and is most often used to hide the player during an intro) and finally calls clear_transfer_info, a method we're just about to look at.

def clear_transfer_info
    @transferring = false           # Player transfer flag
    @new_map_id = 0                 # Destination map ID
    @new_x = 0                      # Destination X coordinate
    @new_y = 0                      # Destination Y coordinate
    @new_direction = 0              # Post-movement direction
  end


This method...well, it clears transfer info! The @transferring flag is set to false, the destination map ID, x and y coordinates are set to 0, and the post-movement direction is set to 0.

def refresh
    @character_name = actor ? actor.character_name : ""
    @character_index = actor ? actor.character_index : 0
    @followers.refresh
  end


This method is called pretty much any time there's a change to whoever's leading the party, as it may require changes to character graphics and followers. Basically if the actor method returns a value @character_name is set to the actor's character_name, otherwise it's an empty string. The same thing is done with the @character_index, and then the refresh method of the @followers object is called, which we'll see when we look at Game_Followers.

def actor
    $game_party.battle_members[0]
  end


This method determines the value of actor, and returns the first index of the party's battle members lineup. The reason this exists, and for the check in refresh, is that you may have a party that has nobody in it. If we didn't check for an actor being present, we'd get a crash for trying to reference a null object.

def stopping?
    return false if @vehicle_getting_on || @vehicle_getting_off
    return super
  end


This method determines whether the player's movement is stopping; returns false if they're getting on or off a vehicle (because the character is being moved even though they're not moving), otherwise returns the result of the same method in the base class. Note that as Game_Character does not itself have a stopping? method, when this is called it will look at the next base class up and call the one in Game_CharacterBase, which basically just checks to see that the character is neither moving nor jumping.

def reserve_transfer(map_id, x, y, d = 2)
    @transferring = true
    @new_map_id = map_id
    @new_x = x
    @new_y = y
    @new_direction = d
  end


This method reserves a map transfer for the player, passing in a map ID, desired X and Y coordinate, and a post-transfer direction which defaults to 2 if no value is supplied.

The @transferring flag is set to true and the @new_whatever instance variables are set to whichever values were passed in. Note that the default of 2 for direction means that the player will always face down after a transfer unless you specify otherwise.

def transfer?
    @transferring
  end


This is simply syntactic sugar to make it clear that we're checking whether the player has a transfer reserved.

def perform_transfer
    if transfer?
      set_direction(@new_direction)
      if @new_map_id != $game_map.map_id
        $game_map.setup(@new_map_id)
        $game_map.autoplay
      end
      moveto(@new_x, @new_y)
      clear_transfer_info
    end
  end


This method performs the actual transfer: if a transfer is reserved, the character's direction is set to the new one. If the new map ID isn't the same as the map the player is currently on, the new map is set up and the autoplay method for the BGM/BGS is called. After any necessary initialisation is done, we call moveto passing in the new coordinates, and then clear transfer info.

def map_passable?(x, y, d)
    case @vehicle_type
    when :boat
      $game_map.boat_passable?(x, y)
    when :ship
      $game_map.ship_passable?(x, y)
    when :airship
      true
    else
      super
    end
  end


This method determines whether a given coordinate is passable in a given direction, which might look familiar as it's an override of the one from Game_CharacterBase with extra checks for vehicles. If the player is in a boat, the result is taken as a call to boat_passable? in $game_map. If the player is in a ship, ship_passable? is called. Passability is implicitly taken as given if the player is in an airship (which means if you're making a game where there are tiles airships can't travel on, you need to change the logic here and possibly write an airship_passable? method), and if no vehicle is being ridden the base version is called.

def vehicle
    $game_map.vehicle(@vehicle_type)
  end


This method gets the vehicle currently being ridden, and simply passes @vehicle_type to the vehicle method of $game_map and returns the result.

def in_boat?
    @vehicle_type == :boat
  end


This method determines whether the player is in a boat, by checking whether the @vehicle_type is the symbol :boat.

def in_ship?
    @vehicle_type == :ship
  end


Same thing, for a ship.

def in_airship?
    @vehicle_type == :airship
  end


Do I even need to say it?

def normal_walk?
    @vehicle_type == :walk && !@move_route_forcing
  end


This method determines whether the player is walking normally; basically true if the vehicle type is :walk and the move route isn't being forced.

def dash?
    return false if @move_route_forcing
    return false if $game_map.disable_dash?
    return false if vehicle
    return Input.press?(:A)
  end


This method determines whether the player is dashing. Returns false if a move route is being forced, dash is disabled, or the player is in a vehicle. Returns true if the player is pressing the :A key (note that which key this is can be changed in the game settings, it is NOT The actual A key on the keyboard. It's actually shift by default).

def debug_through?
    $TEST && Input.press?(:CTRL)
  end


This method determines whether the debug through mode is active. $TEST is a flag which is set to true automatically when you click test play. If this is true and the player is pressing the :CTRL key (which actually is the CTRL key on the keyboard) this method will also return true. As you may remember, this results in the player being able to pass through anything.

def collide?(x, y)
    !@through && (pos?(x, y) || followers.collide?(x, y))
  end


This method detects collision with an obstacle at a given coordinate, including follower collisions. Returns true if through is false AND the player is either at the specified coordinates or would collide with a follower there. (I'm honestly not sure why they didn't use pos_nt here, since it would eliminate the need for a separate !@through check. If anyone knows a reason they did this, please let me know).

def center_x
    (Graphics.width / 32 - 1) / 2.0
  end


This method gets the X coordinate of the screen's center. It's calculated as the total width of the screen divided by 32, minus 1, divided by 2.0. Graphics.width is usually 544, which means that generally speaking this method will return 8.

def center_y
    (Graphics.height / 32 - 1) / 2.0
  end


Same thing for the center Y. Usually 416, so this one will generally return 6.

def center(x, y)
    $game_map.set_display_pos(x - center_x, y - center_y)
  end


This method sets the map's display position to the center of the screen, by calling set_display_pos on $game_map and passing in the passed-in x minus the center_x, and passed-in y minus the center_y. In my opinion this method (as well as some other methods/structures in the default scripts) somewhat invalidates one of the principles of object-oriented programming, which is separation of concerns. The player object shouldn't be responsible for calculating or setting map coordinates.

def moveto(x, y)
    super
    center(x, y)
    make_encounter_count
    vehicle.refresh if vehicle
    @followers.synchronize(x, y, direction)
  end


This method moves the player to another specified location, and overrides the one in Game_CharacterBase. First calls the base method, then centers the map on the new X/Y position, calls the method for calculating encounters, refreshes the vehicle if the player is in one, and synchronises the player's followers for the new x, y and player's direction.

def increase_steps
    super
    $game_party.increase_steps if normal_walk?
  end


This is an overwrite of the base increase_steps method. Calls the base version, then calls the increase_steps method of $game_party if the player is walking normally. The second check is so that the step counter isn't increased when the player is being moved by an event.

def make_encounter_count
    n = $game_map.encounter_step
    @encounter_count = rand(n) + rand(n) + 1
  end


This method generates the number of steps required to start a random encounter. Sets a temporary variable called n to the result of calling the encounter_step method of $game_map (which gets the encounter steps setting from the map properties) then sets the instance variable @encounter_count to a random number from 0-n plus a random number from 0-n + 1. For example, if the setting in the map properties is 30 and the random numbers generated are 12 and 6, @encounter_count will be 19.

def make_encounter_troop_id
    encounter_list = []
    weight_sum = 0
    $game_map.encounter_list.each do |encounter|
      next unless encounter_ok?(encounter)
      encounter_list.push(encounter)
      weight_sum += encounter.weight
    end
    if weight_sum > 0
      value = rand(weight_sum)
      encounter_list.each do |encounter|
        value -= encounter.weight
        return encounter.troop_id if value < 0
      end
    end
    return 0
  end


This method creates the group ID for an encountered troop. First, encounter_list is initialised as an empty array and weight_sum is set to 0.

We iterate through the result of the encounter_list method of $game_map (basically just the list of possible encounters in the map properties) assigning the iteration variable "encounter" to each one. We go to the next element unless encounter_ok? returns true when passing in encounter (we'll see this in a sec). encounter is added to the encounter_list array, then weight_sum is increased by the encounter's defined weight.

If weight_sum is greater than 0, value is set to a random number from 0 to weight_sum. We iterate through everything in encounter_list, again assigning the iteration variable encounter. Value is decreased by the encounter's weight, and we return the encounter's troop ID if value is less than 0. Finally, we return 0.

To take an example, let's say we have a map on which you can encounter Slime*2 with a weight of 10, Hornet*2 with a weight of 5, and Bat*2 with a weight of 15. We'll assume that encounter_ok? returns true for slime and hornet, but not bat as the player isn't on the right region.

First time around the first .each, "Slime*2" is pushed to encounter_list, and weight_sum is now 10. Second time, "Hornet*2" is pushed, and weight_sum is now 15. Third time, we skip Bat as encounter_ok? was false.

weight_sum is greater than 0, so we go through the next bit. Let's say the value generated for value is 2.

In the first iteration, encounter is "Slime*2"; value is decreased by 10, and we return that troop's ID because the value is now less than 0. We're fighting Slimes, guys.

def encounter_ok?(encounter)
    return true if encounter.region_set.empty?
    return true if encounter.region_set.include?(region_id)
    return false
  end


This method checks whether a given encounter is possible. Returns true if the encounter's region set is empty, as that means it can be encountered anywhere. Also returns true if the encounter's region sets include the player's region ID. Otherwise, returns false.

def encounter
    return false if $game_map.interpreter.running?
    return false if $game_system.encounter_disabled
    return false if @encounter_count > 0
    make_encounter_count
    troop_id = make_encounter_troop_id
    return false unless $data_troops[troop_id]
    BattleManager.setup(troop_id)
    BattleManager.on_encounter
    return true
  end


This method processes an encounter. Returns false if the map's interpreter is running, since that means events are processing and that prevents encounters. Also returns false if encounters are disabled in the system settings. Returns false if @encounter_count is greater than 0, as this means the required number of steps haven't been made yet. Following these initial checks, we call make_encounter_count to get the new encounter steps value, set troop_id to make_encounter_troop_id, and returns false unless there's a troop in $data_troops with that ID. Finally, we call the setup and on_encounter methods of BattleManager, and return true.

def start_map_event(x, y, triggers, normal)
    return if $game_map.interpreter.running?
    $game_map.events_xy(x, y).each do |event|
      if event.trigger_in?(triggers) && event.normal_priority? == normal
        event.start
      end
    end
  end


This method triggers map events in a given coordinate with given triggers. We return if the interpreter is running, since this means an event is already in progress. Then we iterate through each event at that coordinate, giving it the iteration variable "event": if the event's trigger is included in the passed-in triggers and calling normal_priority? on the event returns the same value as was passed in to normal (which will be true if we're checking for events on the same level as the hero, false otherwise) we call start on the event. We'll see these methods when we look at Game_Event.

def check_event_trigger_here(triggers)
    start_map_event(@x, @y, triggers, false)
  end


This method checks whether a same-position event has triggered and simply calls start_map_event with the player's X and Y, whatever triggers were passed in, and false (since generally the player can only overlap events either below or above them).

def check_event_trigger_there(triggers)
    x2 = $game_map.round_x_with_direction(@x, @direction)
    y2 = $game_map.round_y_with_direction(@y, @direction)
    start_map_event(x2, y2, triggers, true)
    return if $game_map.any_event_starting?
    return unless $game_map.counter?(x2, y2)
    x3 = $game_map.round_x_with_direction(x2, @direction)
    y3 = $game_map.round_y_with_direction(y2, @direction)
    start_map_event(x3, y3, triggers, true)
  end


This method checks whether a frontal-collision event has triggered. First calls start_map_event passing in the next tile next to the player in their direction of travel, whatever triggers were passed in, and true. We return if any event is starting, and unless the next tile is a counter.

Then we call start_map_event again with the next tile in the player's direction and the same triggers/flag. This allows the player to activate events which are on the other side of a counter tile.

def check_event_trigger_touch(x, y)
    start_map_event(x, y, [1,2], true)
  end


This method checks whether a touch event has triggered by calling start_map_event at the given X and Y, passing in triggers of 1 and 2 (player touch or event touch) and true.

def move_by_input
    return if !movable? || $game_map.interpreter.running?
    move_straight(Input.dir4) if Input.dir4 > 0
  end


This method processes directional key input for movement. Returns if the player isn't movable or the map's interpreter is running, then calls move_straight passing in the direction corresponding to the pressed key if it's greater than 0.

def movable?
    return false if moving?
    return false if @move_route_forcing || @followers.gathering?
    return false if @vehicle_getting_on || @vehicle_getting_off
    return false if $game_message.busy? || $game_message.visible
    return false if vehicle && !vehicle.movable?
    return true
  end


This method determines whether the player can move. Returns false if the player is already moving, then returns false if the move route is forced or followers are gathering, then returns false if the player is getting on or off a vehicle, then returns false if the message window is busy or visible, then returns false if the player is in a vehicle and the vehicle is immovable, and finally returns true.

def update
    last_real_x = @real_x
    last_real_y = @real_y
    last_moving = moving?
    move_by_input
    super
    update_scroll(last_real_x, last_real_y)
    update_vehicle
    update_nonmoving(last_moving) unless moving?
    @followers.update
  end


This is the player's per-frame update method. First, the player's last_real_x and last_real_y are set to their respective values. last_moving is set to the result of moving?, move_by_input is called, the base class's update method is called, update_scroll is called, update_vehicle is called, update_nonmoving is called passing in last_moving unless the player is moving, and finally followers are updated. We'll look at most of the methods we haven't already covered shortly.

def update_scroll(last_real_x, last_real_y)
    ax1 = $game_map.adjust_x(last_real_x)
    ay1 = $game_map.adjust_y(last_real_y)
    ax2 = $game_map.adjust_x(@real_x)
    ay2 = $game_map.adjust_y(@real_y)
    $game_map.scroll_down (ay2 - ay1) if ay2 > ay1 && ay2 > center_y
    $game_map.scroll_left (ax1 - ax2) if ax2 < ax1 && ax2 < center_x
    $game_map.scroll_right(ax2 - ax1) if ax2 > ax1 && ax2 > center_x
    $game_map.scroll_up   (ay1 - ay2) if ay2 < ay1 && ay2 < center_y
  end


This method updates the scroll for a passed-in real X and Y coordinate. Basically calculates which direction the map neds to scroll in and scrolls it in that direction. We already looked at all these methods in Game_Map so I'm not going to say too much more about it.

def update_vehicle
    return if @followers.gathering?
    return unless vehicle
    if @vehicle_getting_on
      update_vehicle_get_on
    elsif @vehicle_getting_off
      update_vehicle_get_off
    else
      vehicle.sync_with_player
    end
  end


This method updates vehicles. Returns if followers are gathering (since this presumably means there's no vehicle to update). Returns unless the player is in a vehicle, for obvious reasons. If the player is getting on a vehicle, calls update_vehicle_get_on. If the player is getting off a vehicle, calls update_vehicle_get_off. Otherwise, calls the sync_with_player method of the vehicle.

def update_vehicle_get_on
    if !@followers.gathering? && !moving?
      @direction = vehicle.direction
      @move_speed = vehicle.speed
      @vehicle_getting_on = false
      @transparent = true
      @through = true if in_airship?
      vehicle.get_on
    end
  end


This method updates the player getting on a vehicle. If followers aren't gathering and the player isn't moving, @direction is set to the vehicle's direction, @move_speed is set to the vehicle's speed, @vehicle_getting_on is set to false (since the player has now entered the vehicle), @transparent is set to true so the player graphic is no longer visible, @through is set to true if the player is in the airship, and the get_on method of the vehicle is called.

def update_vehicle_get_off
    if !@followers.gathering? && vehicle.altitude == 0
      @vehicle_getting_off = false
      @vehicle_type = :walk
      @transparent = false
    end
  end


This method updates the player getting off of a vehicle. If followers aren't gathering and the altitude of the vehicle is 0 (which means it's a boat, ship or airship that just landed), @vehicle_getting_off is set to false (as the player has now exited the vehicle), @vehicle_type is set to :walk, and @transparent is set to false so the player graphic is visible again.

def update_nonmoving(last_moving)
    return if $game_map.interpreter.running?
    if last_moving
      $game_party.on_player_walk
      return if check_touch_event
    end
    if movable? && Input.trigger?(:C)
      return if get_on_off_vehicle
      return if check_action_event
    end
    update_encounter if last_moving
  end


This method processes when the player isn't moving. Returns if the map's interpreter is running. If the passed-in parameter is true (in other words, the player was previously moving), we call the on_player_walk method of $game_party and then return if the player has activated a touch event. After this, if the player can move and the player is pressing :C, we return if the player is entering or exiting a vehicle, or if the player has activated an action event. Finally, we call update_encounter if the player was previously moving.

def update_encounter
    return if $TEST && Input.press?(:CTRL)
    return if $game_party.encounter_none?
    return if in_airship?
    return if @move_route_forcing
    @encounter_count -= encounter_progress_value
  end


This method updates encounters. We return if the player is in test play and pressing CTRL (since encounters don't update during test play phasing), if encounters are turned off, if the player is in an airship, and if the player's move route is being forced. Finally, we reduce @encounter_count by encounter_progress_value, which we'll be looking at next.

def encounter_progress_value
    value = $game_map.bush?(@x, @y) ? 2 : 1
    value *= 0.5 if $game_party.encounter_half?
    value *= 0.5 if in_ship?
    value
  end


This method determines encounter progress value. It's initially set to 2 if the player is in a bush, 1 otherwise. It's halved if the party has the encounter half ability, and halved again if the player's in the ship. This will give you a result of 2 (full encounter rate), 1 (if encounter rate is halved, the player is in a ship, or full rate if the player isn't in a bush), 0.5 (not in a bush with half encounter or ship) or 0.25 (not in a bush with half encounter and on a ship).

def check_touch_event
    return false if in_airship?
    check_event_trigger_here([1,2])
    $game_map.setup_starting_event
  end


This method determines if a touch event has triggered. Returns false if the player is in an airship, then calls check_event_trigger_here passing in 1 and 2 (hero/event touch) and then calls the setup_starting_event method of $game_map.

def check_action_event
    return false if in_airship?
    check_event_trigger_here([0])
    return true if $game_map.setup_starting_event
    check_event_trigger_there([0,1,2])
    $game_map.setup_starting_event
  end


This method determines if an action event has triggered. Returns false if the player is in an airship, then calls check_event_trigger_here passing in 0 (action key). Returns true if setup_starting_event returns true. If not, calls check_event_trigger_there passing in 0, 1 and 2 (action key or hero/event touch) and finishes with another call to setup_starting_event.

def get_on_off_vehicle
    if vehicle
      get_off_vehicle
    else
      get_on_vehicle
    end
  end


This method enters/exits a vehicle. If the player is in a vehicle, gets off the vehicle. Otherwise gets on. Simple.

def get_on_vehicle
    front_x = $game_map.round_x_with_direction(@x, @direction)
    front_y = $game_map.round_y_with_direction(@y, @direction)
    @vehicle_type = :boat    if $game_map.boat.pos?(front_x, front_y)
    @vehicle_type = :ship    if $game_map.ship.pos?(front_x, front_y)
    @vehicle_type = :airship if $game_map.airship.pos?(@x, @y)
    if vehicle
      @vehicle_getting_on = true
      force_move_forward unless in_airship?
      @followers.gather
    end
    @vehicle_getting_on
  end


This method boards a vehicle. First, sets front_x and front_y to the tile in front of the player. Checks which vehicle is in that tile and sets the @vehicle_type accordingly. If the player is now in a vehicle, sets @vehicle_getting_on to true, forces a move forward unless the player's in the airship (which boards on the same tile), and gathers followers. Finally, returns the value of @vehicle_getting_on.

def get_off_vehicle
    if vehicle.land_ok?(@x, @y, @direction)
      set_direction(2) if in_airship?
      @followers.synchronize(@x, @y, @direction)
      vehicle.get_off
      unless in_airship?
        force_move_forward
        @transparent = false
      end
      @vehicle_getting_off = true
      @move_speed = 4
      @through = false
      make_encounter_count
      @followers.gather
    end
    @vehicle_getting_off
  end


This method gets off of a vehicle. Checks if the vehicle is able to land in the current direction on the current coordinates. Sets the direction of the player to down if in an airship. Followers are synchronised, and the get_off method of the vehicle is called. Unless the player is in the airship, we force a move forward and set @transparent to false. @vehicle_getting_off is set to true, @move_speed is set to 4, @through is set to false, make_encounter_count is called, followers are gathered, and finally we return the value of @vehicle_getting_off.

def force_move_forward
    @through = true
    move_forward
    @through = false
  end


Forces a move forward by setting the through flag on, moving one tile forward, and setting through back off.

def on_damage_floor?
    $game_map.damage_floor?(@x, @y) && !in_airship?
  end


Determines whether the player is on a damaging floor by calling the damage_floor? method of $game_map on the player's coordinates, with a logical AND checking the player isn't in an airship.

def move_straight(d, turn_ok = true)
    @followers.move if passable?(@x, @y, d)
    super
  end


Overwrite of the base move_straight method which also moves followers if the player's tile is passable, then calls the base method.

def move_diagonal(horz, vert)
    @followers.move if diagonal_passable?(@x, @y, horz, vert)
    super
  end


Same thing but for diagonal movement.

And finally, we're done with Game_Player!

Game_Follower

This class, which inherits from Game_Character, handles followers, the party members who follow the leader around on the map. It's referenced in the Game_Followers class, which we'll look at after this one.

def initialize(member_index, preceding_character)
    super()
    @member_index = member_index
    @preceding_character = preceding_character
    @transparent = $data_system.opt_transparent
    @through = true
  end


The constructor takes in a member index and a preceding character, which is the character before the newly-created follower in the party. First, we call the base class constructor, set the @member_index instance variable to the index passed in, set @preceding_character to the value passed in, @transparent to the option that was set in system options, and sets @through to true since followers phase through obstacles.

def refresh
    @character_name = visible? ? actor.character_name : ""
    @character_index = visible? ? actor.character_index : 0
  end


This method refreshes a follower. The @character_name is set to the actor's graphic filename if the follower is visible, and an empty string otherwise. The same thing is done to set @character_index to the actor's index or 0.

def actor
    $game_party.battle_members[@member_index]
  end


This method gets the follower's corresponding actor, returned as the @member_index element of $game_party's battle_members property.

def visible?
    actor && $game_player.followers.visible
  end


This method determines whether the follower is visible; returns true if there's a corresponding actor AND the player's followers are visible.

def update
    @move_speed     = $game_player.real_move_speed
    @transparent    = $game_player.transparent
    @walk_anime     = $game_player.walk_anime
    @step_anime     = $game_player.step_anime
    @direction_fix  = $game_player.direction_fix
    @opacity        = $game_player.opacity
    @blend_type     = $game_player.blend_type
    super
  end


This method updates the follower, and basically just sets all of their variables for move speed, transparency etc. to the same values as the player. It then calls the update method of the base class.

def chase_preceding_character
    unless moving?
      sx = distance_x_from(@preceding_character.x)
      sy = distance_y_from(@preceding_character.y)
      if sx != 0 && sy != 0
        move_diagonal(sx > 0 ? 4 : 6, sy > 0 ? 8 : 2)
      elsif sx != 0
        move_straight(sx > 0 ? 4 : 6)
      elsif sy != 0
        move_straight(sy > 0 ? 8 : 2)
      end
    end


This method causes the follower to follow the character preceding it. Unless the character is already moving, its x/y distance from the preceding character is calculated. If the x distance and y distance are both not zero, the follower moves diagonally based on whether the distances are greater than 0. Otherwise, if the x distance is not zero, the follower moves straight left (positive) or right (negative). Otherwise, if the y distance is not zero, the follower moves up (positive) or down (negative).

def gather?
    !moving? && pos?(@preceding_character.x, @preceding_character.y)
  end


This method determines whether the follower is not moving AND is in the same position as the preceding character.

That was a nice small class after all the big ones we've had.

Game_Followers

This is a wrapper for an array of followers. It doesn't inherit from anything else.

attr_accessor :visible                  # Player Followers ON?


A single instance variable, visible, determines whether the player's followers are visible or not.

def initialize(leader)
    @visible = $data_system.opt_followers
    @gathering = false                    # Gathering processing underway flag
    @data = []
    @data.push(Game_Follower.new(1, leader))
    (2...$game_party.max_battle_members).each do |index|
      @data.push(Game_Follower.new(index, @data[-1]))
    end
  end


The constructor takes in a leader parameter, which is the character leading the party. @visible is set to true or false depending on what option was chosen in the system tab for follower visibility, @gathering is set to false, @data is set to an empty array and then a new instance of Game_Follower is pushed to it with an index of 1 and a preceding character of the party leader. Then we iterate from 2 to the maximum number of battle members with "index", pushing to the @data array a new instance of Game_Follower with an index of the iteration variable's value, and a preceding character of the last element of @data.

def [](index)
    @data[index]
  end


This method just allows us to reference followers by a square bracketed index as if it were an array, and returns the index element of @data.

def each
    @data.each {|follower| yield follower } if block_given?
  end


This method shortcuts iteration on followers: it calls each on @data with "follower", yielding follower if a block was given to the call. This allows us to use $game_player.followers.each with a block without having to explicitly reference the internal data variable.

def reverse_each
    @data.reverse.each {|follower| yield follower } if block_given?
  end


Same thing, but iterates through the followers backwards. As we'll see, this is used when followers are moving, so that later followers don't end up moving too far ahead of the ones behind them.

def refresh
    each {|follower| follower.refresh }
  end


This method refreshes followers by calling each on the followers and calling each one's refresh method.

def update
    if gathering?
      move unless moving? || moving?
      @gathering = false if gather?
    end
    each {|follower| follower.update }
  end


The per-frame update method. If the followers are gathering, call move unless moving? OR moving? ...uh, I must admit I don't get why this OR is there. I mean, they're going to return the same value so it's redundant. Feel free to let me know if I'm missing something there.

def move
    reverse_each {|follower| follower.chase_preceding_character }
  end


As I mentioned previously, this method moves the followers by iterating them in reverse and calling each follower's chase_preceding_character method.

def synchronize(x, y, d)
    each do |follower|
      follower.moveto(x, y)
      follower.set_direction(d)
    end
  end


This method synchronises followers to a given coordinate/direction; iterates through each follower then moves it to the provided coordinate and sets its direction to the passed-in value.

def gather
    @gathering = true
  end


This method declares that party members are gathering, and simply sets @gathering to true.

def gathering?
    @gathering
  end


And this one checks whether the followers are gathering by returning the value of @gathering!

def visible_folloers
    @data.select {|follower| follower.visible? }
  end


As stated in the comments, folloers is a typo which was retained for compatibility. Anyway, this one gets an array of displayed followers for iterating through @data and returning an array of followers for whom a call to visible? returns true.

def moving?
    visible_folloers.any? {|follower| follower.moving? }
  end


This method determines whether followers are moving, and returns true if any follower returns true when calling its moving? method.

def gather?
    visible_folloers.all? {|follower| follower.gather? }
  end


This method determines whether followers have gathered by iterating through all visible followers and returning true if all of them return true when their gather? method is called (this basically means all followers are on the same tile)

def collide?(x, y)
    visible_folloers.any? {|follower| follower.pos?(x, y) }
  end


This method determines whether any followers have collided with a given coordinate by iterating through the visible followers and returning true if any of the followers are at the given tile.

And that's us for followers!

Game_Vehicle

This class handles vehicles, which are actually also characters and as such inherit from Game_Character. If a vehicle isn't present on the map, its coordinates will be (-1, -1) as stated in the comments.

attr_reader   :altitude                 # altitude (for airships)
  attr_reader   :driving                  # driving flag


We have two public instance variables in this one: altitude, which is used for ships, and driving, a flag used to show when a vehicle is being ridden.

def initialize(type)
    super()
    @type = type
    @altitude = 0
    @driving = false
    @direction = 4
    @walk_anime = false
    @step_anime = false
    @walking_bgm = nil
    init_move_speed
    load_system_settings
  end


The vehicle constructor takes as its parameter a vehicle type, which will either be :boat, :ship, or :airship. It calls the constructor of its base class, initialises all of the instance variables (note that direction by default is 4, or facing left) then calls init_move_speed and load_system_settings.

def init_move_speed
    @move_speed = 4 if @type == :boat
    @move_speed = 5 if @type == :ship
    @move_speed = 6 if @type == :airship
  end


This method initialises the move speed of a vehicle: 4 for a boat, 5 for a ship, 6 for an airship. If you ever want to change the speeds of your vehicles, this is the place for it.

def system_vehicle
    return $data_system.boat    if @type == :boat
    return $data_system.ship    if @type == :ship
    return $data_system.airship if @type == :airship
    return nil
  end


This method gets the system settings for the vehicle. Returns the appropriate property from $data_system depending on the vehicle's type.

def load_system_settings
    @map_id           = system_vehicle.start_map_id
    @x                = system_vehicle.start_x
    @y                = system_vehicle.start_y
    @character_name   = system_vehicle.character_name
    @character_index  = system_vehicle.character_index
  end


This method loads system settings for the vehicle; its starting map ID, coordinates, graphic name and index.

def refresh
    if @driving
      @map_id = $game_map.map_id
      sync_with_player
    elsif @map_id == $game_map.map_id
      moveto(@x, @y)
    end
    if @type == :airship
      @priority_type = @driving ? 2 : 0
    else
      @priority_type = 1
    end
    @walk_anime = @step_anime = @driving
  end


The vehicle refresh method. If the vehicle is being driven, @map_id is set to the current map ID as obtained from $game_map, and sync_with_player is called. Otherwise, if the starting map ID is the same as the current map, the vehicle is moved to its starting coordinate. If the vehicle's type is airship, @priority_type is set to 2 if it's being driven, 0 otherwise (so it's below hero on the ground and above hero in the air). Otherwise priority type is 1 (same level as hero). Finally, @walk_anime is set to @step_anime which is set to the value of @driving, true or false.

def set_location(map_id, x, y)
    @map_id = map_id
    @x = x
    @y = y
    refresh
  end


This method changes the position of the vehicle. It's kind of like moveto but takes a map ID as well so you can move the vehicle to another map. Simply sets the map ID and coordinates to the passed-in values and calls refresh. This is only used in the "set vehicle location" event command, as we'll see in Game_Interpreter.

def pos?(x, y)
    @map_id == $game_map.map_id && super(x, y)
  end


This method checks whether the vehicle is present at a given tile; return true if the vehicle's map ID is the same as the player's AND the base class version of pos? returns true.

def transparent
    @map_id != $game_map.map_id || super
  end


This method determines whether the vehicle is transparent; returns true if the vehicle's map ID isn't the player's OR the base class version of transparent returns true.

def get_on
    @driving = true
    @walk_anime = true
    @step_anime = true
    @walking_bgm = RPG::BGM.last
    system_vehicle.bgm.play
  end


This method boards a vehicle. Sets @driving, @walk_anime and @step_anime to true (starts the vehicle animating since it's being ridden now), sets the @walking_bgm to the last playng BGM, and then plays the vehicle's BGM.

def get_off
    @driving = false
    @walk_anime = false
    @step_anime = false
    @direction = 4
    @walking_bgm.play
  end


This method exits a vehicle. Sets @driving, @walk_anime and @step_anime to false, the direction to 4 (left) and plays the previous walking BGM.

def sync_with_player
    @x = $game_player.x
    @y = $game_player.y
    @real_x = $game_player.real_x
    @real_y = $game_player.real_y
    @direction = $game_player.direction
    update_bush_depth
  end


This method synchronises the vehicle with the player. Sets the vehicle's x, y, real x and real y to the same values as the player, the direction to the player's direction, and then calls update_bush_depth.

def speed
    @move_speed
  end


This method gets the vehicle's speed, and simply returns @move_speed. Literally the only reason for this method's existence is to save you 5 keystrokes, since move_speed is an attr_reader anyway.

def screen_y
    super - altitude
  end


This method gets the vehicle's screen Y coordinate, and is an overwrite of the base method. Returns the value of a call to the superclass method minus the vehicle's altitude, which is only relevant for airships.

def movable?
    !moving? && !(@type == :airship && @altitude < max_altitude)
  end


Overwrite of movable? for vehicles. Returns true if the vehicle is not moving AND a check for both the type being airship AND the altitude being less than the max_altitude being true. To put this into English, the vehicle is considered immovable if it's already moving or if it's an airship and its altitude is less than the max, since that means it's either on the ground or ascending.

def update
    super
    update_airship_altitude if @type == :airship
  end


The frame update method for vehicles calls the superclass update and then calls update_airship_altitude if the vehicle's type is airship.

def update_airship_altitude
    if @driving
      @altitude += 1 if @altitude < max_altitude && takeoff_ok?
    elsif @altitude > 0
      @altitude -= 1
      @priority_type = 0 if @altitude == 0
    end
    @step_anime = (@altitude == max_altitude)
    @priority_type = 2 if @altitude > 0
  end


This method updates the airship's altitude; if the vehicle is being driven, altitude is increased by 1 if the altitude is less than the max and takeoff is possible. Otherwise, if the altitude is greater than 0, altitude is decreased by 1 and then priority type is set to 0 if altitude is 0 (since the airship is now on the ground). @step_anime is set to the value of checking whether altitude is at max (true if it is, false if it isn't) and priority type is set to 2 if altitude is greater than 0 (above hero if the airship is in the air).

def max_altitude
    return 32
  end


This method gets the maximum altitude for the airship, which is hardcoded at 32 pixels. Feel free to change this if you want your airships to hover higher or lower. You could even recode things slightly so that pressing certain keys can raise/lower the airship as the player wants, which is a feature some commercial RPGs have.

def takeoff_ok?
    $game_player.followers.gather?
  end


This method determines whether the airship can take off, and simply checks whether the player's followers are gathered. The reason for this is that when the player is boarding the airship the followers all converge on the player's tile, and the airship can't take off until they're all there. If we didn't have this check the airship would take off without your party members!

def land_ok?(x, y, d)
    if @type == :airship
      return false unless $game_map.airship_land_ok?(x, y)
      return false unless $game_map.events_xy(x, y).empty?
    else
      x2 = $game_map.round_x_with_direction(x, d)
      y2 = $game_map.round_y_with_direction(y, d)
      return false unless $game_map.valid?(x2, y2)
      return false unless $game_map.passable?(x2, y2, reverse_dir(d))
      return false if collide_with_characters?(x2, y2)
    end
    return true
  end


This method determines whether landing is possible on a given tile in a given direction. If the vehicle is an airship, we return false unless airship_land_ok? returns true for the tile in question, and also unless there are no events present on the tile. If the vehicle isn't an airship, we find the next tile forward from the current one and check both whether the new coordinate is a valid tile, whether it's passable, or whether moving there would collide with a character. All of these checks will return false. Finally, if we haven't returned a value from any of the checks, we return true.

Just one more to go this episode, guys!

Game_Event

This class handles events, which like pretty much all of the other classes we've looked at so far are characters and inherit from Game_Character.

attr_reader   :trigger                  # trigger
  attr_reader   :list                     # list of event commands
  attr_reader   :starting                 # starting flag


The public instance variables for this class are :trigger, which is the trigger set for the event; :list, which is a list of event commands; and :starting, a flag denoting whether the event is starting.

def initialize(map_id, event)
    super()
    @map_id = map_id
    @event = event
    @id = @event.id
    moveto(@event.x, @event.y)
    refresh
  end


The constructor takes a map ID and event, which is an instance of the RPG::Event class. The superclass constructor is called, @map_id and @event are set to the passed-in values, and moveto is called for the event's coordinates. Then, we call the refresh method.

def init_public_members
    super
    @trigger = 0
    @list = nil
    @starting = false
  end


The overwrite to the init_public_members method calls the superclass version and then sets the variables that were added for events.

def init_private_members
    super
    @move_type = 0                        # Movement type
    @erased = false                       # Temporary erase flag
    @page = nil                           # Event page
  end


Another overwrite, this one calls super and then sets the event-specific private variables. @move_type is obviously the event's movement type; @erased is a temporary flag used when an event is being erased; @page is the page of the event which is currently being processed.

def collide_with_characters?(x, y)
    super || collide_with_player_characters?(x, y)
  end


This method overwrites the base version of the character collision check by adding another check for the event colliding with player characters.

def collide_with_player_characters?(x, y)
    normal_priority? && $game_player.collide?(x, y)
  end


And this is the one which checks that. Returns true if the event is set to "same level as hero" and calling the collide? method of $game_player returns true.

def lock
    unless @locked
      @prelock_direction = @direction
      turn_toward_player
      @locked = true
    end
  end


This method locks an event for processing. Unless it's locked already, sets its prelock direction to the event's current direction, turns it toward the player, and sets locked to true.

def unlock
    if @locked
      @locked = false
      set_direction(@prelock_direction)
    end
  end


This method unlocks an event. If it's locked, sets locked to false and sets its direction to the direction it was facing before it locked.

These two methods are used to make sure events turn towards the player when activated and don't turn back until whatever processing they do is finished.

def update_stop
    super
    update_self_movement unless @move_route_forcing
  end


Overwrite to update_stop, calls the base method and then calls update_self_movement unless the move route is being forced.

def update_self_movement
    if near_the_screen? && @stop_count > stop_count_threshold
      case @move_type
      when 1;  move_type_random
      when 2;  move_type_toward_player
      when 3;  move_type_custom
      end
    end
  end


Updates the event during automatic movement; if the event is near the visible area of the screen and its stop count is greater than the auto-movement stop count threshold (more on that in a sec), we call move_type_random if the event's movement type is "random movement", move_type_toward_player if it's set to "approach" and move_type_custom if it's set to "custom".

def near_the_screen?(dx = 12, dy = 8)
    ax = $game_map.adjust_x(@real_x) - Graphics.width / 2 / 32
    ay = $game_map.adjust_y(@real_y) - Graphics.height / 2 / 32
    ax >= -dx && ax <= dx && ay >= -dy && ay <= dy
  end


This method determines if an event is near the visible area of the screen, taking in the number of tiles left/right and number of tiles up/down of the screen's center. By default, we look 12 tiles to the side and 8 tiles vertically.

ax is set to the adjusted real X coordinate - half the width of the screen divided by 32. ay is set to the same thing but for Y.

The return value will be true if the event's adjusted coordinates fall inside the range specified in the method call.

def stop_count_threshold
    30 * (5 - @move_frequency)
  end


This method determines the auto-movement stop count threshold, hardcoded as 30 multiplied by (5 minus the event's move frequency). Taking an event with a default frequency of 3, this would be 30 * (5 - 3) = 30 * 2 = 60, so the event will wait 60 frames (1 second) before taking its next step.

def move_type_random
    case rand(6)
    when 0..1;  move_random
    when 2..4;  move_forward
    when 5;     @stop_count = 0
    end
  end


This method moves the event randomly by generating a random number from 0-5. If it's 0 or 1, the event takes a random step. If it's 2 to 4, the event simply moves forward. If it's 5, the event's stop count is set to 0.

def move_type_toward_player
    if near_the_player?
      case rand(6)
      when 0..3;  move_toward_player
      when 4;     move_random
      when 5;     move_forward
      end
    else
      move_random
    end
  end


This method is used to take a step for an event set to move toward the player, and also generates a number from 0-5 if the event is considered to be near the player. It it's 0 to 3, moves the event toward the player. If it's 4, moves it a step in a random direction. If it's 5, moves the event forward. If the event is NOT near the player, it just moves randomly.

def near_the_player?
    sx = distance_x_from($game_player.x).abs
    sy = distance_y_from($game_player.y).abs
    sx + sy < 20
  end


This method determines whether the event is considered to be near the player; returns true if the combined x and y distance is less than 20.

def move_type_custom
    update_routine_move
  end


This method moves the event when its type is set to custom and calls update_routine_move from the Game_Character class.

def clear_starting_flag
    @starting = false
  end


This method clears the event's starting flag, and simply sets @starting to false.

def empty?
    !@list || @list.size <= 1
  end


This method determines whether the event is empty of contents; returns true if the list variable isn't set OR its size is less than or equal to 1 (since events always have 1 command, which is the end of the event)

def trigger_in?(triggers)
    triggers.include?(@trigger)
  end


This method determines if the event's trigger is included in a passed-in array of values and returns true if that array includes the event's trigger.

def start
    return if empty?
    @starting = true
    lock if trigger_in?([0,1,2])
  end


This method starts an event. Returns if it's empty, as there's no code to run. Following that check, we set @starting to true and lock if its trigger is action key, player touch or event touch.

def erase
    @erased = true
    refresh
  end


This method handles temporarily erasing an event; sets @erased to true and calls the refresh method.

def refresh
    new_page = @erased ? nil : find_proper_page
    setup_page(new_page) if !new_page || new_page != @page
  end


The refresh method for events sets a variable called new_page to nil if @erased is true, or the result of find_proper_page otherwise. Then calls setup_page passing in the new page if there's no new page OR new_page isn't equal to the current page.

def find_proper_page
    @event.pages.reverse.find {|page| conditions_met?(page) }
  end


This method finds the first page which meets its conditions by iterating through the event's pages in reverse order and calling the find method on them, using the iteration variable "page" and returning the first one for which conditions_met? is true. (the fact that it's done in reverse is why events run the last page with its conditions met and work backwards to the first).

def conditions_met?(page)
    c = page.condition
    if c.switch1_valid
      return false unless $game_switches[c.switch1_id]
    end
    if c.switch2_valid
      return false unless $game_switches[c.switch2_id]
    end
    if c.variable_valid
      return false if $game_variables[c.variable_id] < c.variable_value
    end
    if c.self_switch_valid
      key = [@map_id, @event.id, c.self_switch_ch]
      return false if $game_self_switches[key] != true
    end
    if c.item_valid
      item = $data_items[c.item_id]
      return false unless $game_party.has_item?(item)
    end
    if c.actor_valid
      actor = $game_actors[c.actor_id]
      return false unless $game_party.members.include?(actor)
    end
    return true
  end


This method determines whether a given event page has had its conditions met.

A temporary variable called c is set to the page's condition; if the first "switch" condition is checked, returns false unless the switch with the chosen ID is on. The same check is performed for the second switch condition. The next check is whether the "variable" condition is checked, and returns false if that variable's value is less than the value specified. Then the "self switch" condition is checked; the key is set to and we return false if that key in the self switches list isn't true. (if you'd forgotten from when we looked at it, self switches internally store their keys as an array consisting of the map ID the event is on, the event's ID, and the self switch's letter. You're not actually limited to A, B, C and D in code; you can use literally any value you like)

def setup_page(new_page)
    @page = new_page
    if @page
      setup_page_settings
    else
      clear_page_settings
    end
    update_bush_depth
    clear_starting_flag
    check_event_trigger_auto
  end


This method sets up a new event page, for a passed-in value which will be an instance of RPG::Event::Page. @page is set to the passed-in value. If there's a page in the variable, we call setup_page_settings, otherwise we call clear_page_settings. After this, we call update_bush_depth, clear_starting_flag, and check_event_trigger_auto.

def clear_page_settings
    @tile_id          = 0
    @character_name   = ""
    @character_index  = 0
    @move_type        = 0
    @through          = true
    @trigger          = nil
    @list             = nil
    @interpreter      = nil
  end


This method clears an event's page settings. Sets its tile ID to 0, graphic name to a blank string, character index to 0, move type to 0, through to true, trigger to nil, command list to nil, and interpreter to nil.

def setup_page_settings
    @tile_id          = @page.graphic.tile_id
    @character_name   = @page.graphic.character_name
    @character_index  = @page.graphic.character_index
    if @original_direction != @page.graphic.direction
      @direction          = @page.graphic.direction
      @original_direction = @direction
      @prelock_direction  = 0
    end
    if @original_pattern != @page.graphic.pattern
      @pattern            = @page.graphic.pattern
      @original_pattern   = @pattern
    end
    @move_type          = @page.move_type
    @move_speed         = @page.move_speed
    @move_frequency     = @page.move_frequency
    @move_route         = @page.move_route
    @move_route_index   = 0
    @move_route_forcing = false
    @walk_anime         = @page.walk_anime
    @step_anime         = @page.step_anime
    @direction_fix      = @page.direction_fix
    @through            = @page.through
    @priority_type      = @page.priority_type
    @trigger            = @page.trigger
    @list               = @page.list
    @interpreter = @trigger == 4 ? Game_Interpreter.new : nil
  end


This method sets up the event page's settings. Sets the tile ID to the page's graphic's tile ID (which will be 0 if the graphic isn't a tile), graphic name to the chosen charset, and index to the index on that charset. If the event's original direction is different to the page graphic's chosen direction, it's set to that value instead. Same thing with the original animation pattern. The other settings are set to whatever was chosen on the page (I'm not going through them all) and then if the event's trigger is "parallel process" @interpreter is set to a new instance of Game_Interpreter, as parallel process events actually have their own interpreter (which is why they can process their commands at the same time as other events)

def check_event_trigger_touch(x, y)
    return if $game_map.interpreter.running?
    if @trigger == 2 && $game_player.pos?(x, y)
      start if !jumping? && normal_priority?
    end
  end


This event checks whether the event has been triggered as a touch event at a given coordinate. Returns if the map's interpreter is running. If the current event's trigger is "event touch" and the player is at the given coordinate, we call the event's start method if it's not jumping and is at the same level as the hero.

def check_event_trigger_auto
    start if @trigger == 3
  end


This method checks whether autorun events should start, and calls the event's start method if its trigger is set to "autostart".

def update
    super
    check_event_trigger_auto
    return unless @interpreter
    @interpreter.setup(@list, @event.id) unless @interpreter.running?
    @interpreter.update
  end


The per-frame update method of events calls the update method of the superclass, then calls check_event_trigger_auto (which is why autostart events will process before any other kind). We return unless there's an interpreter present. After this check, we call the interpreter's setup method passing in the event's command list and ID unless the interpreter is running already, and then we update the interpreter.

And that's it! We're done! Finally! I think I've written a small novel here, and I'm sure you can now appreciate why it's taken me several months to get this episode out of the door. I'm gonna continue with this until I finish, but I'll be working on my RPG Maker MV teardown as well, so updates may be sporadic for the near future. But for now, it is once again my great pleasure to say...

...until next time.

Posts

Pages: 1
I was wondering if you were going to continue the series. I'm happy to see you have. I wonder if someone will do a series like this but for javascript, now that MV is released, but it's great to see Ace still getting love. ^.^)b
Trihan
"It's more like a big ball of wibbly wobbly...timey wimey...stuff."
3359
I'm going to be doing a Javascript series for MV as well, Lib. :)
Nice! I look forward to it~ >.<)b
Trihan
"It's more like a big ball of wibbly wobbly...timey wimey...stuff."
3359
Working on the first episode now. ;)
Pages: 1