SLIP INTO RUBY - UNDER THE HOOD PART 8: END OF BATTLE

In which battlers are done now.

  • Trihan
  • 04/28/2015 02:00 PM
  • 2001 views
I'm running out of inventive ways to greet sports fans. Imagine I said something incredibly witty like I usually do. Once again it's time for



Game_Enemy

Game_Enemy, like Game_Actor, inherits from Game_Battler, and so has the same basic properties and methods.

Public instance variables ahoy!

attr_reader   :index                    # index in troop
  attr_reader   :enemy_id                 # enemy ID
  attr_reader   :original_name            # original name
  attr_accessor :letter                   # letters to be attached to the name
  attr_accessor :plural                   # multiple appearance flag
  attr_accessor :screen_x                 # battle screen X coordinate
  attr_accessor :screen_y                 # battle screen Y coordinate


So we've got three attr_readers and four attr_accessors. I'd like to think that the comments will explain their purposes without me having to specify.

def initialize(index, enemy_id)
    super()
    @index = index
    @enemy_id = enemy_id
    enemy = $data_enemies[@enemy_id]
    @original_name = enemy.name
    @letter = ""
    @plural = false
    @screen_x = 0
    @screen_y = 0
    @battler_name = enemy.battler_name
    @battler_hue = enemy.battler_hue
    @hp = mhp
    @mp = mmp
  end


The constructor takes an index and enemy_id as parameters. First it calls the initialize method of the superclass, then sets the instance variables to the appropriate default values. In addition to setting the attr_readers and accessors, To set @original_name we have to look up the name of the enemy's data object. In addition to the public instance variables, we also set the enemy's battler name, hue, HP and MP.

def enemy?
    return true
  end


This method overwrites the one from Game_BattlerBase and returns true (as in this case, the battler is indeed an enemy).

def friends_unit
    $game_troop
  end


The overwritten friends_unit returns $game_troop...

def opponents_unit
    $game_party
  end


...and the overwritten opponents_unit returns $game_party.

def enemy
    $data_enemies[@enemy_id]
  end


Get the enemy's data object by using its ID.

def feature_objects
    super + [enemy]
  end


Gets an array of all objects which have features. Calls super (which returns states) and adds the enemy as an array element.

def name
    @original_name + (@plural ? letter : "")
  end


This method gets the name of the enemy. Returns @original_name and the enemy's letter if there is more than one of the same enemy in the battle (we'll see how this works when we get to Game_Troop).

def param_base(param_id)
    enemy.params[param_id]
  end


This method overwrite returns the enemy's params for the given param ID instead of 0.

def exp
    enemy.exp
  end


This is a getter method for the enemy's experience value (how much exp it gives for killing it).

def gold
    enemy.gold
  end


This is a getter method for how much gold the enemy drops.

def make_drop_items
    enemy.drop_items.inject([]) do |r, di|
      if di.kind > 0 && rand * di.denominator < drop_item_rate
        r.push(item_object(di.kind, di.data_id))
      else
        r
      end
    end
  end


This method determines what items the enemy will drop. Iterates through all of the possible items the enemy drops with an inject (starting with an empty array) with r as the memo variable and di as the current drop.

If the kind of the drop item is greater than 0 (in other words, one has been set in the database) AND a random number between 0 and 1 multiplied by the denominator of the drop item is less than the item drop rate, the item's object is pushed to r, otherwise we return r (as nothing is added to the array).

def drop_item_rate
    $game_party.drop_item_double? ? 2 : 1
  end


This method determines the item drop rate: if the party has double drop rate it returns 2, or 1 otherwise.

Let's have a quick example. Let's say that the enemy drops Potion at a 1/10 rate. The denominator will be 10, obviously. Let's say the random number generated is 0.5793868440309804, the multiplied value would be 5.79. Obviously that's not less than 1, so the party doesn't get the item. Had the generated value been anything less than 0.1 the item would have been obtained.

def item_object(kind, data_id)
    return $data_items  [data_id] if kind == 1
    return $data_weapons[data_id] if kind == 2
    return $data_armors [data_id] if kind == 3
    return nil
  end


This method determines a drop item's object with a given kind and data ID. Returns the relevant item object if kind is 1, weapon object if kind is 2, or armor object if kind is 3. Data ID is obviously the number of the item from the database.

def use_sprite?
    return true
  end


Unlike actors, enemies do use sprites in battle, so this method returns true.

def screen_z
    return 100
  end


This method gets the enemy's Z coordinate on the battle screen. By default it's always 100.

def perform_damage_effect
    @sprite_effect_type = :blink
    Sound.play_enemy_damage
  end


This method performs the enemy damage effect. Sets the sprite effect type to blink (again, more on this when we get to the Sprite classes) and plays the enemy damage sound effect.

def perform_collapse_effect
    case collapse_type
    when 0
      @sprite_effect_type = :collapse
      Sound.play_enemy_collapse
    when 1
      @sprite_effect_type = :boss_collapse
      Sound.play_boss_collapse1
    when 2
      @sprite_effect_type = :instant_collapse
    end
  end


This method performs the enemy's collapse effect. Checks the value of the collapse_type variable; if it's 0, the collapse tpe is the normal :collapse and the collapse sound effect is played. If it's 1, the effect type is a boss collapse and we play the collapse1 boss effect. If it's 2, we just collapse the enemy instantly (note that you can set this as a trait for enemies).

def transform(enemy_id)
    @enemy_id = enemy_id
    if enemy.name != @original_name
      @original_name = enemy.name
      @letter = ""
      @plural = false
    end
    @battler_name = enemy.battler_name
    @battler_hue = enemy.battler_hue
    refresh
    make_actions unless @actions.empty?
  end


This method transforms the enemy into another one, using a given enemy ID. The enemy's ID is set the supplied value. If the name of the new enemy object is different from the old enemy's original name, its original name is set to that of the new object, its letter is set to a blank string, and plural is set to false. Then, the battler filename and hue are set to the new ones, the enemy is refreshed, and the enemy's action list is created if it isn't empty.

def conditions_met?(action)
    method_table = {
      1 => :conditions_met_turns?,
      2 => :conditions_met_hp?,
      3 => :conditions_met_mp?,
      4 => :conditions_met_state?,
      5 => :conditions_met_party_level?,
      6 => :conditions_met_switch?,
    }
    method_name = method_table[action.condition_type]
    if method_name
      send(method_name, action.condition_param1, action.condition_param2)
    else
      true
    end
  end


This method determines whether the conditions have been met for a given action (which will be of the RPG::Enemy::Action class). If you look at the source code for this class in the help file, you can see that the condition type variable takes values which correspond to the method table here: 0 is "Always", 1 is "Turn No.", 2 is "HP", 3 is "MP", 4 is "State", 5 is "Party Level" and 6 is "Switch".

method_name is set to the element of method_table corresponding to the action's condition type. If there is a value in method_name, we call send for the given method name, supplying the action condition parameters. Otherwise, we simply return true (because the only other possibility is that the condition is 0, in which case we always want the enemy to use the action).

send is a built-in Ruby method which calls a given method with given parameters. Let's say the condition is "Turn No." and it was set to 1 + 3*X; this is a shorthand way of saying "condition_met_turns?(1, 3)"

def conditions_met_turns?(param1, param2)
    n = $game_troop.turn_count
    if param2 == 0
      n == param1
    else
      n > 0 && n >= param1 && n % param2 == param1 % param2
    end
  end


This method determines whether turn number conditions have been met. A variable called n is set to the turn count from $game_troop (which is basically the number of battle turns that have elapsed). If param2 is equal to 0, we return true if n is equal to param1; otherwise, we return true if n is greater than 0 AND n is greater than or equal to param1 AND n divided by param2 gives a remainder equal to that of param1 divided by param2.

Say whaaaa? Okay, example time. Again, we'll use the case where the condition was "Turn No." with "1" and "3" as parameters. If we're in, say, turn 4, n is set to 4. param2 is not equal to 0, so we process the "else". n is greater than 0 AND it's greater than or equal to 1, so far so good. 4 % 3 gives 1, and 1 % 3 does too. So we return true.

Had the turn been, say, 6, the first two conditions would still have passed but 6 % 3 returns 0 so the last one is false, making the condition as a whole false, and so we return false because the conditions haven't been met.

def conditions_met_hp?(param1, param2)
    hp_rate >= param1 && hp_rate <= param2
  end


This method determines whether HP conditions have been met. Returns true if the enemy's HP percentage is greater than or equal to param1 AND less than or equal to param2. Say you set the action's condition to HP between 50 and 75%: for an enemy with 500 max HP who is currently at 100HP, hp_rate is 0.25, param1 is 0.5 and param2 is 0.75. 0.25 is not greater than or equal to 0.5, so we return false.

def conditions_met_mp?(param1, param2)
    mp_rate >= param1 && mp_rate <= param2
  end


Exactly the same thing but for MP.

def conditions_met_state?(param1, param2)
    state?(param1)
  end


This method determines whether state conditions are met. Returns true if the enemy has the state param1, which is whatever state the developer picked from the dropdown. Note that even though state in the action conditions box of the database only takes one value, due to the structure of the method which calls this one we still have to pass in a param2 parameter. Nothing's done with it, but if it wasn't there we'd get an error because the method call should have had two parameters.

def conditions_met_party_level?(param1, param2)
    $game_party.highest_level >= param1
  end


This method determines whether the party level condition is met. Returns true if the highest levelled member in the party has a level greater than or equal to param1. Note that the tooltip for this option says "average party level" but this is not actually the case.

def conditions_met_switch?(param1, param2)
    $game_switches[param1]
  end


This method determines whether a switch condition has been met. Returns true if the switch defined by param1 is on.

def action_valid?(action)
    conditions_met?(action) && usable?($data_skills[action.skill_id])
  end


This method determines whether the given action (again, RPG::Enemy::Action) is valid. Returns true if conditions_met? is true AND the action's skill ID is usable by the enemy.

def select_enemy_action(action_list, rating_zero)
    sum = action_list.inject(0) {|r, a| r += a.rating - rating_zero }
    return nil if sum <= 0
    value = rand(sum)
    action_list.each do |action|
      return action if value < action.rating - rating_zero
      value -= action.rating - rating_zero
    end
  end


This method randomly selects an enemy action from the ones available to it. Takes as its parameters an action_list (an array of RPG::Enemy::Action) and a value to consider as rating 0.

The variable sum is set to the result of calling inject on the action list with r as the memo variable and a as the current iteration. Each iteration adds the action's rating - rating_zero to the sum. We return nil if sum is less than or equal to zero (because there are no usable actions if this is the case). If we pass that check, we set a variable called value to a random integer from 0 to sum, then iterate through each member of action_list with "action". We return action if value is less than the action's rating - rating_zero. If value is equal to or higher, we reduce value by the action's rating - rating_zero.

def make_actions
    super
    return if @actions.empty?
    action_list = enemy.actions.select {|a| action_valid?(a) }
    return if action_list.empty?
    rating_max = action_list.collect {|a| a.rating }.max
    rating_zero = rating_max - 3
    action_list.reject! {|a| a.rating <= rating_zero }
    @actions.each do |action|
      action.set_enemy_action(select_enemy_action(action_list, rating_zero))
    end
  end


This is the method which actually figures out which actions CAN be chosen. First, we call super, which populates @actions with a fresh instance of Game_Action for each action that can be taken. We return if @actions is empty; if there are no actions, why bother going further?

action_list is then set to the result of a select on the enemy object's actions list, with "a"; the action is included if it's considered valid. Again, we return if action_list is empty because there's no point in continuing if this is the case.

rating_max is set to the maximum value from calling collect on action_list and taking the action's rating as each element in the returned array. Basically, whichever rating is highest from the enemy's usable actions.

rating_zero is set to rating_max - 3.

I'm not sure if I've covered this method in any previous editions, but reject is another handy method for arrays/hashes, and is basically the opposite of select. It iterates through the given Enumerable and REMOVES the element if the given condition returns true. In this case, we're checking whether the action's rating is less than or equal to rating_zero: if it is, we don't want it.

Finally, we iterate through each element of @actions. For each one, we call set_enemy_action, supplying as the argument the result of calling select_enemy_action with the final action list and rating_zero as arguments.

Is it example time? It's example time. Starting in make_actions:

Let's take a relatively interesting enemy, like, say...Sahagin. By default, Sahagin has three battle actions: Attack (Always, Rating 5), Forget Cloud (Always, Rating 4) and Wave (Always, Rating 5)

The monster has no traits giving it additional actions, so after calling super @actions is a one-element array containing a single instance of Game_Action. @actions is not empty, so we pass the first return statement.

We iterate through enemy.actions and select the ones for which the action is valid. Assuming that the Sahagin is not currently incapacitated and possesses the requisite MP for its spells, action_list is now a 3-element array containing the RPG::Enemy::Action instances pertaining to Attack, Forget Cloud and Wave. action_list is not empty, so we pass the second return statement.

Now we iterate through action_list, using collect to return a new array of their ratings; this gives us [5, 4, 5] and we take the maximum value from there to put into rating_max, so its value is now 5. rating_zero is set to max - 3, so our "zero" rating is 2.

We iterate through action_list, rejecting any whose rating is less than or equal to 2. None of the actions meet this criteria, so nothing is removed.

For each element of @action (which in this case will only run once) we call set_enemy_action on that action, giving it select_enemy_action as an argument, which itself is given the arguments action_list (which as we've just established is an array with 3 instances of RPG::Enemy::Action in it) and rating_zero, which is 2.

In select_enemy_action, we iterate through action_list, starting with a memo value of 0. The first iteration gives us r += 5 - 2, so r is now 3. The second iteration gives us r += 4 - 2, so r is now 5. The third iteration again gives us r += 5 - 2, so r's final value is 8 and we place that value into sum. Sum is not less than or equal to 0, so we pass the first return statement.

Value is set to a random number between 0 and 8 (which will actually give us 0-7 as rand is non-inclusive). Let's say the number chosen is 3.

We again iterate through each element of action_list, so we're starting with Attack. Attack's rating - rating_zero is 3; value is not less than 3 (as it's equal) so we pass the second return statement.

Value is reduced by rating - rating_zero, so value is now 0.

Now in the iteration for Forget Cloud, value is indeed less than rating - rating_zero (which gives us 2) so the enemy will decide to use Forget Cloud this turn.

That's it for Game_Enemy! Though this code should point out one interesting thing you may not have been aware of: if you have any enemy actions with a priority more than 3 below the highest one you've set, that action will NEVER be chosen. That means if you have, say, Attack with rating 10, any actions with a rating less than 7 will be completely ignored.

Game_Actors

After the slogfests of the battlers, we have a nice easy class now. Game_Actors is simply a wrapper for an actor array. It's referenced via $game_actors.

def initialize
    @data = []
  end


Constructor, empty array. You know the drill.

def [](actor_id)
    return nil unless $data_actors[actor_id]
    @data[actor_id] ||= Game_Actor.new(actor_id)
  end


And this is for getting an element out of the array. Returns nil unless there's an actor object in the database with the given ID. If it passes that, it returns the actor_id element of @data; if the data for it hasn't already been set, it instead returns a new instance of Game_Actor for that ID (this is important. If we didn't check whether a value was already set it would return a new actor every time we were trying to read an actor's details).

That is literally it! Two methods.

Game_Unit

This one's a bit meatier, but still fairly short compared to some of the ones we've just looked at. Game_Unit is the superclass of Game_Party and Game_Troop. Let's take a look under its hood.

Only one public instance variable this time.

attr_reader   :in_battle                # in battle flag


Simple flag to determine whether the given unit is currently in a battle. Note that although as a child of this class Game_Troop does actually have an in_battle flag itself, in all of the default scripts it's never used. The reason for this is obvious: as the flag is set to true when a battle begins, the flag for an enemy is always going to be true. They only exist in battle, after all.

def initialize
    @in_battle = false
  end


This is the constructor for units, and simply sets @in_battle to false.

def members
    return []
  end


Method for getting the members of the unit. Returns an empty array (this will be overwritten by the child classes, same as we've seen with several other methods so far).

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


This method gets all members of the unit who are still alive. Runs a select on members with "member" and includes that member in the returned array if the member is alive.

def dead_members
    members.select {|member| member.dead? }
  end


This method gets the dead members. Same thing, but it calls dead? instead of alive?.

def movable_members
    members.select {|member| member.movable? }
  end


This one is the same thing but with movable? instead to get the members who are able to move.

def clear_actions
    members.each {|member| member.clear_actions }
  end


This method clears all actions. Iterates through each member and calls their clear_actions method, which we've already seen.

def agi
    return 1 if members.size == 0
    members.inject(0) {|r, member| r += member.agi } / members.size
  end


This method gets the party's average agility value. Returns 1 if there are no members in the party. Otherwise, calls inject on members with an initial value of 0 and adding the member's agi to the memo variable for each iteration, and when that's done it divides the returned value by the size of members.

def tgr_sum
    alive_members.inject(0) {|r, member| r + member.tgr }
  end


This method gets the total target rate for the unit. Calls inject on alive_members with an initial value of 0, and each iteration adds the member's TGR value.

def random_target
    tgr_rand = rand * tgr_sum
    alive_members.each do |member|
      tgr_rand -= member.tgr
      return member if tgr_rand < 0
    end
    alive_members[0]
  end


This method selects a random target. Sets a variable called tgr_rand to a random number between 0 and 1 multiplied by the TGR sum. Then, we iterate through alive_members with "member", subtracting the current member's TGR from tgr_rand and returning the member if tgr_rand is less than 0. If we've gone through every member without this returning a value, we return the first living member.

Let's say we've got four party members, one of which is a tank who draws enemy attacks towards him, so he has the trait TGR * 200% instead of 100% as is the default for actors. tgr_rand will be a random number between 0 and 1 (for the sake of simplicity, let's say the value generated is 0.5) multiplied by tgr_sum, which will be 5.0 (2.0 for the tank, 1.0 for each other member), so tgr_rand will end up being 2.5.

For each member we subtract the member's TGR from tgr_rand; let's say the tank is first in the party followed by the others. First, tgr_rand is reduced by 2.0, so it's now 0.5. Then, it's reduced by 1.0 and becomes -0.5, so the enemy will target the second party member. To target the tank the random value would have had to be 0.4 or less. Obviously, this means that the higher TGR is the more likely the actor is to be targeted, but it's still not a guarantee.

def random_dead_target
    dead_members.empty? ? nil : dead_members[rand(dead_members.size)]
  end


This method randomly chooses a dead target. Checks whether dead_members is empty: if so, returns nil, otherwise returns a random index of dead_members from 0 up to its sze.

def smooth_target(index)
    member = members[index]
    (member && member.alive?) ? member : alive_members[0]
  end


This method smooths out the target selection with a given index. Sets a variable called member to the index element of members, then checks to see if member has a value AND the member is alive. If so, we return member, otherwise we return the first alive member.

The astute among you might remember this method being called in a class we looked at previously: Game_Action. I explained what it did back then, but this is the actual code that does it.

def smooth_dead_target(index)
    member = members[index]
    (member && member.dead?) ? member : dead_members[0]
  end


This is the same thing, but smooths out a dead target instead.

def clear_results
    members.select {|member| member.result.clear }
  end


This method clears the Game_ActionResult of each member.

def on_battle_start
    members.each {|member| member.on_battle_start }
    @in_battle = true
  end


This method processes the start of a battle. Calls the on_battle_start method of each member, and sets the unit's @in_battle flag to true.

def on_battle_end
    @in_battle = false
    members.each {|member| member.on_battle_end }
  end


This method processes the end of a battle. Sets the @in_battle flag to false and calls the on_battle_end method of each member.

def make_actions
    members.each {|member| member.make_actions }
  end


This method makes the actions for the unit. Calls make_actions for each member of it, which is a method we already looked at.

def all_dead?
    alive_members.empty?
  end


This method checks whether all members of the unit are dead. Returns true if alive_members is empty.

def substitute_battler
    members.find {|member| member.substitute? }
  end


This method gets a battler which takes attacks in the place of low-HP allies. Calls find on members, which will return the first one for which the block member.substitute? returns true (in other words, the first battler with the substitute special flag).

That's it for Game_Unit!

Game_Party

This class handles parties, funnily enough. Stuff like gold and items and the like. It's referenced by the global variable $game_party and as stated previously is a child class of Game_Unit.

First, constants:

ABILITY_ENCOUNTER_HALF    = 0           # halve encounters
  ABILITY_ENCOUNTER_NONE    = 1           # disable encounters
  ABILITY_CANCEL_SURPRISE   = 2           # disable surprise
  ABILITY_RAISE_PREEMPTIVE  = 3           # increase preemptive strike rate
  ABILITY_GOLD_DOUBLE       = 4           # double money earned
  ABILITY_DROP_ITEM_DOUBLE  = 5           # double item acquisition rate


As you may have noticed, these constants correspond to the "party ability" traits.

attr_reader   :gold                     # party's gold
  attr_reader   :steps                    # number of steps
  attr_reader   :last_item                # for cursor memorization:  item


Then we have the public instance variables. They're all attr_readers, and should speak for themselves.

def initialize
    super
    @gold = 0
    @steps = 0
    @last_item = Game_BaseItem.new
    @menu_actor_id = 0
    @target_actor_id = 0
    @actors = []
    init_all_items
  end


The constructor calls the constructor of the superclass and initialises the instance variables. The last item selected is initially set to a new instance of Game_BaseItem; all of the integer variables are set to 0, @actors is set to an empty array, and then we call init_all_items.

def init_all_items
    @items = {}
    @weapons = {}
    @armors = {}
  end


Initialising the items consists of setting @items, @weapons and @armors to empty hashes.

def exists
    !@actors.empty?
  end


This method determines the existence of a party. Returns true if @actors is not empty, false otherwise.

def members
    in_battle ? battle_members : all_members
  end


Overwrite of the superclass's members method. Checks whether the party is in battle: if they are, returns battle_members, otherwise returns all_members.

def all_members
    @actors.collect {|id| $game_actors[id] }
  end


Gets all members of the party. Calls collect on @actors with "id" and takes $game_actors[id] as each element of the returned array.

def battle_members
    all_members[0, max_battle_members].select {|actor| actor.exist? }
  end


Gets all battle members. Calls select on an array of the first X of all_members, where X is the result of max_battle_members (as we'll see in a second) with "actor". The current element will be included in the returned array if the actor exists.

def max_battle_members
    return 4
  end


This method determines the maximum number of party members that can participate in battle (as seen in the previous method). By default, returns 4.

def leader
    battle_members[0]
  end


This method gets the leader of the party, and returns the first battle member.

def items
    @items.keys.sort.collect {|id| $data_items[id] }
  end


This method gets an object array for the party's items. Calls collect on a sorted list of the keys of the @items hash with "id" and takes the item object for each id as each element of the new array.

def weapons
    @weapons.keys.sort.collect {|id| $data_weapons[id] }
  end


This method does pretty much the same thing but only for the party's weapons.

def armors
    @armors.keys.sort.collect {|id| $data_armors[id] }
  end


Same thing again but for armors.

def equip_items
    weapons + armors
  end


This method gets all of the party's equippable items by adding weapons and armors together.

def all_items
    items + equip_items
  end


Similar method to get all the party's items by adding items to equip_items.

def item_container(item_class)
    return @items   if item_class == RPG::Item
    return @weapons if item_class == RPG::Weapon
    return @armors  if item_class == RPG::Armor
    return nil
  end


This method gets the item container object corresponding to a given item class. If the item class is RPG::Item, we return @items. If the class is RPG::Weapon, we return @weapons. And if it's RPG::Armor, we return @armors.

def setup_starting_members
    @actors = $data_system.party_members.clone
  end


This method sets up the initial party by setting @actors to a clone of the party_members property of $data_system, which is the list of initial party members set in the database. The reason it's set to a clone is that @actors will at various points be changed and if it's a reference to the actual data object we would be changing the initial party setup (which obviously wouldn't be a good thing if we go back to the title screen and start a new game; the starting party would be whatever the party was when we quit).

def name
    return ""           if battle_members.size == 0
    return leader.name  if battle_members.size == 1
    return sprintf(Vocab::PartyName, leader.name)
  end


This method gets the name of the party. If there are no battle members, we return an empty string. If there's only one battle member, we return the leader's name. Otherwise, we return an sprintf for Vocab::PartyName, which as you may recall is the string "%s's Party" with the %s replaced by the leader's name.

def setup_battle_test
    setup_battle_test_members
    setup_battle_test_items
  end


This method sets everything up for a test battle. Calls two methods: one that sets up the test battle party, and the other that gives the party the maximum possible number of every item in the game.

def setup_battle_test_members
    $data_system.test_battlers.each do |battler|
      actor = $game_actors[battler.actor_id]
      actor.change_level(battler.level, false)
      actor.init_equips(battler.equips)
      actor.recover_all
      add_actor(actor.id)
    end
  end


This method sets up the test battle party. Iterates through each element of $data_system.test_battlers with "battler" (this is the list of party members you select in the test battle setup window). Sets a variable called actor to the battler.actor_id element of $game_actors, calls the actor's change_level method supplying the battler's level as set in the test battle window and false (for showing levelups), calls the actor's init_equips method with the battler's equipment as set in the window, completely recovers the actor, and finally calls add_actor with the actor's ID as the argument.

Let's say, by way of example, that we're setting up a test battle with just Eric at level 25 with Crimson Axe, Knight Shield and Leather Mail equipped. battler.actor_id is going to be 1, battler.level is going to be 25, and battler.equips is going to be [5, 48, 0, 21, 0]. actor will be set to $game_actors[1], which is the object for Eric. The rest you should be able to loosely follow as we've already looked at all of the methods being looked at except add_actor, which just adds the given ID to @actors and performs a couple of refreshing actions (which we'll see shortly).

def setup_battle_test_items
    $data_items.each do |item|
      gain_item(item, max_item_number(item)) if item && !item.name.empty?
    end
  end


This is the method which adds test battle items. Pretty simple really: it iterates through each element in $data_items with "item" and calls gain_item with the item object and max_item_number as arguments, but only if there's an item in the variable and its name is not empty.

def highest_level
    lv = members.collect {|actor| actor.level }.max
  end


This method finds out the highest level in the party. I'm honestly not sure why it's being assigned to a variable since lv isn't used for anything else.

def add_actor(actor_id)
    @actors.push(actor_id) unless @actors.include?(actor_id)
    $game_player.refresh
    $game_map.need_refresh = true
  end


This method adds an actor to the party. Pushes the given actor_id to @actors unless it's already included, then refreshes $game_player and tells $game_map it needs a refresh too (which will be picked up on its next update, as we'll see in Game_Map two classes from now!)

def remove_actor(actor_id)
    @actors.delete(actor_id)
    $game_player.refresh
    $game_map.need_refresh = true
  end


This method removes an actor; works almost exactly the same as add_actor but deletes the ID from the array instead of pushing it, oddly enough.

def gain_gold(amount)
    @gold = [[@gold + amount, 0].max, max_gold].min
  end


This method adds a given amount of gold to the party's stash. Takes the maximum number between @gold + amount and 0 (so it can't become negative) and then the minimum value between that number and max_gold (so it can't go above max) then assigns that value to @gold.

def lose_gold(amount)
    gain_gold(-amount)
  end


How do we lose gold? Well, we gain gold with a negative amount, basically. That's what this is doing. Yep.

def max_gold
    return 99999999
  end


Here is your max gold. Enjoy it, and spend it wisely.

def increase_steps
    @steps += 1
  end


Increases the party's steps taken by 1. Adds 1 to @steps. These are so complicated.

def item_number(item)
    container = item_container(item.class)
    container ? container[item.id] || 0 : 0
  end


This method gets the number of a given item held by the party. Calls item_container with the item's class and assigns the return value to a variable called container (which is either going to return @items, @weapons or @armors). If a value is assigned to container, we return the item's ID element of container OR 0 if there's no value there. If container is empty, we return 0.

I think this is the first time we've seen a ternary operator with nested logic, so I'll see if I can explain this a bit better. It may help to break up the parts: as I explained originally, a ternary operator takes the form {conditon} ? {value if true} : {value if false}. In this case, {container} ? {container[item.id] || 0} : {0}.

First, we check container to see if it contains any values (it may not if item_container didn't return anything). If it does, we try to assign the item.id element of container to the return value. If that fails, the interpreter looks at the || part and sees 0. This is basically a safety net for not being able to get an item out of the list. Then we have the failure part of the ternary operator (the part after the :) where we also return 0 because there were no items at all.

def max_item_number(item)
    return 99
  end


This method gets the maximum number of an item that can be held. Returns 99.

def item_max?(item)
    item_number(item) >= max_item_number(item)
  end


This method determines whether the party is holding the maximum number of an item. Checks whether item_number(item) returns a value greater than or equal to max_item_number(item).

def has_item?(item, include_equip = false)
    return true if item_number(item) > 0
    return include_equip ? members_equip_include?(item) : false
  end


This method determines whether the party has a given item, with an include_equip flag that will either include or exclude items characters have equipped. Returns true if the party has more than 0 of the item. If that check doesn't return anything, we check whether include_equip is true. If so, it returns the value of members_equip_include?(item) which checks whether any member has that item equipped. If that method also returns false, we return false for has_item?.

def members_equip_include?(item)
    members.any? {|actor| actor.equips.include?(item) }
  end


This method checks whether any party member has the given item equipped. Calls .any? on members, returning true the first actor.equips.include?(item) returns true. If no member has that item equipped, it will return false.

def gain_item(item, amount, include_equip = false)
    container = item_container(item.class)
    return unless container
    last_number = item_number(item)
    new_number = last_number + amount
    container[item.id] = [[new_number, 0].max, max_item_number(item)].min
    container.delete(item.id) if container[item.id] == 0
    if include_equip && new_number < 0
      discard_members_equip(item, -new_number)
    end
    $game_map.need_refresh = true
  end


This method adds the given amount of the given item to the party's inventory. Note that it can take negative values (which will remove the item instead) which is why this method also has an include_equip flag: if you're removing the item and this is set to true, it will remove equipped items if there aren't enough of them in the inventory.

Sets container to the appropriate container for the item's class, and returns unless container has a value.

A variable called last_number is set to the number of the item the party has, and new_number is set to last_number plus the given amount.

We ge the maximum value between new_number and 0 (so we don't end up with -2 of an item), then take the minimum value between that value and max_item_number (so we don't go above the maximum) and assign that value to the item's ID element of container. We then delete the item's ID from the container if the new number held is equal to 0.

If we set the include_equip flag to true, and the new_number is less than 0 (in other words, we're losing more items than we had in inventory) we call discard_members_equip with the item and the negative of the new number (to turn it into a positive). Finally, we let the map know it needs to refresh.

def discard_members_equip(item, amount)
    n = amount
    members.each do |actor|
      while n > 0 && actor.equips.include?(item)
        actor.discard_equip(item)
        n -= 1
      end
    end
  end


This method, as the name may suggest, discards a given number of a given item of equipment from party members.

First, n is set to amount. Then we iterate through each party member with "actor" and while n is greater than 0 and the actor in question has the given item equipped, we call discard_equip with that item, then reduce n by 1.

This indicates that when the game is taking equipped items away from the party, it looks at the members in order, and takes all of the item it can from each member before moving on.

def lose_item(item, amount, include_equip = false)
    gain_item(item, -amount, include_equip)
  end


This is literally nothing more than a shortcut method that prevents you having to call gain_item with negative values for when items are being lost.

def consume_item(item)
    lose_item(item, 1) if item.is_a?(RPG::Item) && item.consumable
  end


And is used for the method which consumes an item! This one loses 1 item from the party inventory if the item is an instance of RPG::Item and it's considered consumable.

def usable?(item)
    members.any? {|actor| actor.usable?(item) }
  end


This method determines whether anyone in the party is able to use a given item or skill. Calls .any? on members with "actor" and returns true for the first actor who can use it.

def inputable?
    members.any? {|actor| actor.inputable? }
  end


A very similar method checking whether any party member is able to input commands in battle. As with usable?, returns true if any member returns true for a call to inputable?.

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


This method checks whether the entire party is dead. Returns true if super (which returns true if alive_members is empty, remember?) AND (the party is in battle OR the number of party members is greater than 0). This allows the method to return true in the case that everyone dies while on the map, but false if you have an empty party for cutscene purposes.

def on_player_walk
    members.each {|actor| actor.on_player_walk }
  end


This method handles processing for each step the player takes. Iterates through each party member with "actor" and calls that member's on_player_walk method.

def menu_actor
    $game_actors[@menu_actor_id] || members[0]
  end


This method gets the actor selected in the menu. Returns the @menu_actor_id element of $game_actors if there is one, or the first party member otherwise.

def menu_actor=(actor)
    @menu_actor_id = actor.id
  end


This method sets the selected actor to the given one. Simply sets @menu_actor_id to the ID of the supplied actor.

def menu_actor_next
    index = members.index(menu_actor) || -1
    index = (index + 1) % members.size
    self.menu_actor = members[index]
  end


This method selects the next actor in the menu. Sets index to the index in members of menu_actor OR -1 if there isn't an index to get.

Index is then set to the remainder of index+1 divided by the number of party members. For example, in a 4-person party if #2 was the last selected (with an index of 1 since they start at 0), index would now be (1+1) % 4, which is 2, or member #3.

Then the party's menu_actor is set to the index element of members.

def menu_actor_prev
    index = members.index(menu_actor) || 1
    index = (index + members.size - 1) % members.size
    self.menu_actor = members[index]
  end


This method is pretty much the same but selects the previous actor instead. Using the previous example again, index starts at 1, and is then set to (1+4-1) % 4, or 4 % 4, which is 0, so we select member 0.

def target_actor
    $game_actors[@target_actor_id] || members[0]
  end


This method gets the actor who was targeted by use of a skill or item. Returns the @target_actor_id element of $game_actors if there is one, or the first party member otherwise.

def target_actor=(actor)
    @target_actor_id = actor.id
  end


This method sets the targeted actor. Works the same way as setting menu_actor.

def swap_order(index1, index2)
    @actors[index1], @actors[index2] = @actors[index2], @actors[index1]
    $game_player.refresh
  end


This method changes the order of party members (used when setting formation) by swapping the indexes. In Ruby, we can use a handy little method called parallel assignment, which basically just sets the given array elements to their reverse. (index1 becomes index2 and index2 becomes index1). In other programming languages, this usually requires using a third "placeholder" variable to hold one of the values first.

def characters_for_savefile
    battle_members.collect do |actor|
      [actor.character_name, actor.character_index]
    end
  end


Ths method determines which character images should be used in a save file display. Performs a collect on battle_members with "actor" and sets as each element of the new array the actor's charset name and index.

def party_ability(ability_id)
    battle_members.any? {|actor| actor.party_ability(ability_id) }
  end


This method determines whether any party member possesses a given party ability. It's a shortcut method for the methods coming up next. Calls .any? on battle_members with "actor" and returns true if any of them have in their features the ID of the given party ability.

def encounter_half?
    party_ability(ABILITY_ENCOUNTER_HALF)
  end


Here's where our constants from the beginning of the class come in! This method checks whether the party has the "Half encounter rate" ability. It should be pretty self-explanatory.

def encounter_none?
    party_ability(ABILITY_ENCOUNTER_NONE)
  end


This one's for checking the "No encounters" ability.

def cancel_surprise?
    party_ability(ABILITY_CANCEL_SURPRISE)
  end


And here's the one for the "No surprise attacks" ability.

def raise_preemptive?
    party_ability(ABILITY_RAISE_PREEMPTIVE)
  end


And this one's for "Raise rate of preemptive attacks"...

def gold_double?
    party_ability(ABILITY_GOLD_DOUBLE)
  end


Double gold earned...and finally...

def drop_item_double?
    party_ability(ABILITY_DROP_ITEM_DOUBLE)
  end


Double item rate.

def rate_preemptive(troop_agi)
    (agi >= troop_agi ? 0.05 : 0.03) * (raise_preemptive? ? 4 : 1)
  end


This method calculates the chance of a preemptive attack, and takes the enemy troop's average agility as a parameter.

The return value is the result of two ternary operators multiplied together. The first one's condition is the party's average agility being higher than or equal to the enemy troop's: if it is, the first value is 0.05, otherwise it's 0.03. The second operator's condition is raise_preemptive? being true. If it is, the second value is 4, otherwise it's 1.

Let's have an example. In this case, the party's average agility is 14, the enemy troop's average agility is 20, and raise_preemptive? is true. 14 is not greater than or equal to 20, so first value is 0.03. raise_preemptive? is true, so the second value is 4. The equation then becomes 0.03 * 4, or a preemptive chance of 0.12 (12%). Note that no matter how high or low the party's average agi is compared to the enemy's, this equation will never give a preemptive chance higher than 20% (0.05 * 4) or lower than 3% (0.03 * 1). In fact, the only possibilities are 20%, 12%, 5% or 3%.

def rate_surprise(troop_agi)
    cancel_surprise? ? 0 : (agi >= troop_agi ? 0.03 : 0.05)
  end


This method calculates the chance of the enemy getting a surprise attack on the party, and takes the enemy troop's average agility as a parameter.

The ternary operators are arranged slightly differently for this one: first, we check whether cancel_surprise? returns true. If so, we return 0 (as there's no chance of a surprise attack with that party ability). If not, we have another ternary operator. The condition for this one is the party's average agi being greater than or equal to the troop's: if so, we return 0.03, otherwise we return 0.05. This means there can only ever be either a 5%, 3% or 0% chance at a surprise attack.

Bliss! We're finally done with Game_Party! Just one more class to go in the units.

Game_Troop

This class handles enemy groups and battle data, as well as battle events. Referenced by the global variable $game_troop.

What kind of class would we be without constants?

LETTER_TABLE_HALF = [' A',' B',' C',' D',' E',' F',' G',' H',' I',' J',
                       ' K',' L',' M',' N',' O',' P',' Q',' R',' S',' T',
                       ' U',' V',' W',' X',' Y',' Z']
  LETTER_TABLE_FULL = ['A','B','C','D','E','F','G','H','I','J',
                       'K','L','M','N','O','P','Q','R','S','T',
                       'U','V','W','X','Y','Z']


There are two letter tables: one is only used when the game is set to use Japanese, as we'll see in a bit.

attr_reader   :screen                   # battle screen state
  attr_reader   :interpreter              # battle event interpreter
  attr_reader   :event_flags              # battle event executed flag
  attr_reader   :turn_count               # number of turns
  attr_reader   :name_counts              # hash for enemy name appearance


The public instance variables are like so. They should all be pretty self-explanatory, but you'll see what they do as we go through the class anyway.

def initialize
    super
    @screen = Game_Screen.new
    @interpreter = Game_Interpreter.new
    @event_flags = {}
    clear
  end


Okay, so the constructor calls the constructor of the superclass (Game_Unit) then sets @screen to a new instance of Game_Screen (a class we've looked at already), @interpreter to a new instance of Game_Interpreter (which is about four or five issues away), and @event_flags to an empty hash before calling the clear method.

def members
    @enemies
  end


The members method of this class returns @enemies.

def clear
    @screen.clear
    @interpreter.clear
    @event_flags.clear
    @enemies = []
    @turn_count = 0
    @names_count = {}
  end


The clear method does exactly what it says on the tin. Clears the screen, clears the interpreter, clears event flags, clears out the enemies, sets the turn count to 0, and the names list to an empty hash.

def troop
    $data_troops[@troop_id]
  end


The troop method gets the troop object from the "Troops" tab of the database for the instance's @troop_id. Each of these objects is an instance of RPG::Troop, which you can look up in the help file.

def setup(troop_id)
    clear
    @troop_id = troop_id
    @enemies = []
    troop.members.each do |member|
      next unless $data_enemies[member.enemy_id]
      enemy = Game_Enemy.new(@enemies.size, member.enemy_id)
      enemy.hide if member.hidden
      enemy.screen_x = member.x
      enemy.screen_y = member.y
      @enemies.push(enemy)
    end
    init_screen_tone
    make_unique_names
  end


This method sets up the troop taking troop_id as a parameter. Calls clear, sets @troop_id to the supplied troop_id, then iterates through each member of the troop with "member".

Goes to the next iteration unless there's an enemy object corresponding to the member's enemy ID, sets enemy to a new instance of Game_Enemy giving the size of @enemies and the member's enemy ID as arguments (this sets the index of each member to the current size of @enemies, which as we'll see in a second grows with each iteration). If the member is set as hidden in the troop tab, we call the enemy's hide method. The enemy's screen_x is set to the member's X as set in the database, and the same with the screen_y. Finally, we push the enemy to @enemies.

After the loop, we call init_screen_tone and make_unique_names.

def init_screen_tone
    @screen.start_tone_change($game_map.screen.tone, 0) if $game_map
  end


The init_screen_tone method calls the start_tone_change method of @screen with $game_map.screen.tone and 0 as arguments but only if $game_map has a value. What this does is makes sure the screen tone in battle is the same as it was on the map.

def make_unique_names
    members.each do |enemy|
      next unless enemy.alive?
      next unless enemy.letter.empty?
      n = @names_count[enemy.original_name] || 0
      enemy.letter = letter_table[n % letter_table.size]
      @names_count[enemy.original_name] = n + 1
    end
    members.each do |enemy|
      n = @names_count[enemy.original_name] || 0
      enemy.plural = true if n >= 2
    end
  end


This method adds letters to the names of enemies if there is more than one of the same kind present.

We iterate through each element of members with "enemy". Go to the next element unless enemy.alive? because there's no point in renaming a dead enemy. Go to the next element unless enemy.letter.empty? because if the enemy already has a letter we don't need to give it one. n is set to the enemy's original name key of @names_count OR 0 if there's no corresponding key. The enemy's letter property is set to the index of letter_table corresponding to the remainder of dividing n by the size of the letter table. Then the enemy's original name key of @names_count is set to n + 1.

After this, we iterate through each member again, again with "enemy" n is set to the enemy's original name key of @names_count OR 0, then enemy.plural is set to true if n is greater than or equal to 2.

The reason there are two separate each calls here is that we're adding to the number of a given enemy present in the first one, so we don't know until it's finished exactly how many of a given enemy there are. If we had everything in the first loop, it actually would work, but this makes it a bit cleaner and easy to understand.

As with most things, it's probably best to give an example. Let's take a troop consisting of Slime x 2. We've just started a battle.

The first iteration for Slime #1 will pass the next checks, because the enemy is alive and doesn't have a letter. n is set to 0 because @names_count has no data yet. enemy.letter is set to the element of letter_table corresponding to 0 % 26, which is 0 ("A") and @names_count["Slime"] is increased by 1.

The second iteration for Slime #2 will also pass the next checks. n is set to 1 because @names_count["Slime"] is 1, then enemy.letter is set to the element of letter_table corresponding to 1 % 26, which is 1 ("B") and @names_count["Slime"] is again increased by 1.

Then we have the other loop; in both cases it will set n to 2 and enemy.plural will be set to true.

def letter_table
    $game_system.japanese? ? LETTER_TABLE_FULL : LETTER_TABLE_HALF
  end


This method determines which letter table to use. If the system is using Japanese, we use LETTER_TABLE_FULL. Otherwise, we use LETTER_TABLE_HALF.

def update
    @screen.update
  end


This method is called on each update and simply updates @screen.

def enemy_names
    names = []
    members.each do |enemy|
      next unless enemy.alive?
      next if names.include?(enemy.original_name)
      names.push(enemy.original_name)
    end
    names
  end


This method gets an array of the enemy names. We set names to an empty array, then iterate through each element of members with "enemy". We go to the next element unless enemy.alive? because we don't want to include dead or hidden enemies. We also go to the next element if names already includes the original name of the current enemy. If we passed those two checks, we push the enemy's original name to names. After the loop, we return the value of names, which will now be a unique array of all enemy names in the battle (which is used to display which enemies you've just encountered when battle starts).

def conditions_met?(page)
    c = page.condition
    if !c.turn_ending && !c.turn_valid && !c.enemy_valid &&
       !c.actor_valid && !c.switch_valid
      return false      # Conditions not set: not executed
    end
    if @event_flags[page]
      return false      # Executed
    end
    if c.turn_ending    # At turn end
      return false unless BattleManager.turn_end?
    end
    if c.turn_valid     # Number of turns
      n = @turn_count
      a = c.turn_a
      b = c.turn_b
      return false if (b == 0 && n != a)
      return false if (b > 0 && (n < 1 || n < a || n % b != a % b))
    end
    if c.enemy_valid    # Enemy
      enemy = $game_troop.members[c.enemy_index]
      return false if enemy == nil
      return false if enemy.hp_rate * 100 > c.enemy_hp
    end
    if c.actor_valid    # Actor
      actor = $game_actors[c.actor_id]
      return false if actor == nil 
      return false if actor.hp_rate * 100 > c.actor_hp
    end
    if c.switch_valid   # Switch
      return false if !$game_switches[c.switch_id]
    end
    return true         # Condition met
  end


This monster of a method checks whether conditions have been met for a given battle event page.

A variable called c is set to the page's conditions. If the condition is not "at end of turn" AND is not "turn no." AND is not "enemy" AND is not "actor" AND is not "switch", we return false (these are the only valid conditions after all).

If the page key of @event_flags is true, return false (the event's already been executed, we don't want to run it again).

If the condition is "at end of turn" we return false unless BattleManager is in its end turn phase.

If the condition is "turn no." we set n to @turn_count, a to the first turn value and b to the second turn value. We return false if b is 0 AND n is not equal to a. We also return false if b is greater than 0 AND (n is less than 1 OR n is less than a OR n % b is not equal to a % b).

This one seems more complex, so let's have an example. Let's say turn no. is set to 1+2*X (every 2 turns starting at turn 1), and we're on turn 3.

n is set to 3, a is set to 1, b is set to 2.

We pass the first check because b is not equal to 0 and therefore the AND is false.

For the second check, b is greater than 0, so the check might fail; let's look at the OR block. Is n less than 1? No. Is n less than a? No. Is n % b not equal to a % b? Well n % b would be 3 % 2 which is 1, and a % b would be 1 % 2 which is 1, so they're equal. None of the OR conditions were true, so the if is false and the check passes.

If the condition is "enemy", enemy is set to the enemy object corresponding to the index chosen when setting up the condition. We return false if enemy is nil, and we also return false if the enemy's HP percentage is greater than the value set in the condition. Note that battle conditions are one of the few places a percentage is held internally as its actual value instead of a decimal, so the enemy's HP rate needs to be multiplied by 100 to compare them properly.

If the condition is "actor" we do pretty much the same thing but with an actor object instead.

If the condition is "switch" we return false if the specified switch ID is off.

If we pass all of these checks, we return true as the condition for the page has obviously been met.

Note that as RPG::Troop::Page::Condition considers the presence of all of these conditions true/false values, you can have as many or as few conditions for a battle event page as you want.

def setup_battle_event
    return if @interpreter.running?
    return if @interpreter.setup_reserved_common_event
    troop.pages.each do |page|
      next unless conditions_met?(page)
      @interpreter.setup(page.list)
      @event_flags[page] = true if page.span <= 1
      return
    end
  end


This method sets up a battle event. We return if @interpreter is running as we don't want to interrupt events that are happening already. We also return if the interpreter has a common event reserved, as we don't want to interrupt that either.

If we pass those checks, we iterate through each event page present in the troop with "page". We go to the next element unless the conditions for that page are met. If we pass that check, we call the @interpreter's setup method with page.list as the argument (which is the set of event commands in the page) and set @event_flags for that page to true if the page span is less than or equal to 1 (which will either be 0: battle or 1: turn, which are only run once per battle or once per turn respectively)

def increase_turn
    troop.pages.each {|page| @event_flags[page] = false if page.span == 1 }
    @turn_count += 1
  end


This method increases the turn count in battle. First, for each troop event page with "page", we set @event_flags for that page to false if its span is "turn", then increase @turn_count by 1.

def exp_total
    dead_members.inject(0) {|r, enemy| r += enemy.exp }
  end


This method calculates the total experience the party will get from slain enemies. Performs an inject on dead_members with an intiial value of 0 and adding the enemy's exp value for each dead enemy.

def gold_total
    dead_members.inject(0) {|r, enemy| r += enemy.gold } * gold_rate
  end


This method calculates the total gold obtained. Performs an inject, same as the one above but for gold instead, and multiplies the final value by gold_rate, which we'll look at next.

def gold_rate
    $game_party.gold_double? ? 2 : 1
  end


This method determines the gold rate. If the party has the "double gold" party ability, returns 2, or 1 otherwise.

def make_drop_items
    dead_members.inject([]) {|r, enemy| r += enemy.make_drop_items }
  end


This method makes the troop's drop list. Performs an inject on each dead enemy with an initial value of a blank array, then adds the result of the enemy's make_drop_items method. This will end up being an array of every item the dead enemies dropped, which can then be displayed to the player and added to inventory.

Phew! It's been a long slog, but we're finally done with the game classes for battlers. Coming up we've got Game_Map, Game_CommonEvent, Game_Character, Game_Player, Game_Follower, Game_Followers, Game_Vehicle, Game_Event and finally the final boss: Game_Interpreter. Map, CharacterBase, Character and Player are pretty huge so I'm definitely going to have to split things up, but I'm not sure where the cutoffs will be yet. Once we're done with the game objects, we'll be onto the Sprite classes, then Windows, and finally Scenes!

---------------------------------------

EXTRA AWESOME NEW FEATURE!

---------------------------------------

At the behest of TheMysticWyvern in #rpgmaker.net on IRC, I've decided to have a set of little homework exercises in each edition of Slip into Ruby, to test you on what I've covered and make sure you understand the explanations. Feel free to work on these yourself or post your answers in the comments. I'll post my versions of the answers in the next issue. I may or may not include these retroactively for new readers, we'll see how it goes.

Homework for this week!

1. How would I get the exp value of enemy 51 in the database?

2. What would I change to make enemy drop rate x4 instead of x2 when the party has the double drop rate ability?

3. What would I change if I wanted a unit's "agi" method to give the combined value instead of the average?

4. How would I change the rate of preemptive attacks to give twice the chance of a preemptive if the party's agi is higher than or equal to the enemy's?

5. How would I give plural enemies in battle numbers instead of letters?

6. What would I change in the scripts to make it so that every turn in battle, each enemy's ATK increases by 25%?

Until next time.