SLIP INTO RUBY: UNDER THE HOOD PART 7: GAME_ACTOR

In which this class is way too long to look at anything else this week.

  • Trihan
  • 04/26/2015 12:35 PM
  • 3707 views
It's that time again guys! It's time for



Game_Actor

Game_Actor is a subclass of Game_Battler, which we looked at last week. It's used within the Game_Actors class and is also referenced from Game_Party via the global variables $game_actors and $game_party respectively.

attr_accessor :name                     # Name
  attr_accessor :nickname                 # Nickname
  attr_reader   :character_name           # character graphic filename
  attr_reader   :character_index          # character graphic index
  attr_reader   :face_name                # face graphic filename
  attr_reader   :face_index               # face graphic index
  attr_reader   :class_id                 # class ID
  attr_reader   :level                    # level
  attr_reader   :action_input_index       # action number being input
  attr_reader   :last_skill               # For cursor memorization:  Skill


The global instance variable should be pretty familiar by now, and the purposes of these ones shouldn't be too hard to figure out.

def initialize(actor_id)
    super()
    setup(actor_id)
    @last_skill = Game_BaseItem.new
  end


Initialising an instance of this class takes actor_id as a parameter. First we call super (the initialize method of Game_Battler), then call setup supplying actor_id as an argument, and then set @last_skill to a new instance of Game_BaseItem.

def setup(actor_id)
    @actor_id = actor_id
    @name = actor.name
    @nickname = actor.nickname
    init_graphics
    @class_id = actor.class_id
    @level = actor.initial_level
    @exp = {}
    @equips = []
    init_exp
    init_skills
    init_equips(actor.equips)
    clear_param_plus
    recover_all
  end


Setting up an actor involves setting some of the instance variables to aspects of the actor property, which will be defined in a moment. We also call init_graphics, set exp to a blank hash, equips to a blank array, and call init_exp, init_skills, init_equips with actor.equips as the argument, clear_param_plus, and finally recover_all. The latter two are inherited from Game_Battler, so we've already looked at them.

def actor
    $data_actors[@actor_id]
  end


Pretty simple getter for the actor object; simply returns the element of $data_actors corresponding to the current value of @actor_id.

def init_graphics
    @character_name = actor.character_name
    @character_index = actor.character_index
    @face_name = actor.face_name
    @face_index = actor.face_index
  end


Initialising graphics involves setting variables for the charset/index and faceset/index.

def exp_for_level(level)
    self.class.exp_for_level(level)
  end


This method gets the total amount of experience needed to get to the level specified by the level parameter. Calls the method exp_for_level, which is a built-in method of RPG::Class, which every class defined in the database is an instance of. If we look at the code for it we see that the formula to calculate exp required is:

def exp_for_level(level)
    lv = level.to_f
    basis = @exp_params[0].to_f
    extra = @exp_params[1].to_f
    acc_a = @exp_params[2].to_f
    acc_b = @exp_params[3].to_f
    return (basis*((lv-1)**(0.9+acc_a/250))*lv*(lv+1)/
      (6+lv**2/50/acc_b)+(lv-1)*extra).round.to_i
  end


Let's take an example: Eric's Soldier class, and figure out how much experience he needs in total to attain level 7.

lv = 7.0
basis = 30
extra = 20
acc_a = 30
acc_b = 30
return (30 * ((7.0-1) ** (0.9 + 30 / 250)) * 7 * ( 7 + 1 ) /( 6 + 7 ** 2 / 50 / 30 ) + ( 7 - 1 ) * 20 ).round.to_i = 1852, which is the value listed in the database.

def init_exp
    @exp[@class_id] = current_level_exp
  end


The exp initialising method just sets the key @class_id in the @exp hash to the value of current_level_exp.

def exp
    @exp[@class_id]
  end


This method is a getter for the @exp hash, and returns the value associated with the key @class_id. Seeing that @exp is a hash indicates that an actor will actually have a separate exp value for as many classes as they change to.

def current_level_exp
    exp_for_level(@level)
  end


This method gets the minimum experience required for the actor's current @level by calling exp_for_level with that as the argument.

def next_level_exp
    exp_for_level(@level + 1)
  end


Oddly enough, working out the exp for the next level involves calling the same method on @level + 1. Who'd have thought?

def max_level
    actor.max_level
  end


This method returns the max_level attribute of the actor data as set in the database. actor.max_level is an attr_accessor so you can change its value via script.

def max_level?
    @level >= max_level
  end


This is a boolean method which returns true if the value of @level is greater than or equal to the return value of max_level.

def init_skills
    @skills = []
    self.class.learnings.each do |learning|
      learn_skill(learning.skill_id) if learning.level <= @level
    end
  end


Initialising skills involves setting @Skills to an empty array and then iterating through each element of the actor's class's learnings property, which is a built-in instance variable which is itself an array of RPG::Class::Learning, a class with three properties: level, the level at which the skill is learned; skill_id, the ID of the skill; and note, whatever is in the skill's notebox (by which I mean the notebox you get when you set a skill in the class's learning section, not the notetag for the skill itself)

def init_equips(equips)
    @equips = Array.new(equip_slots.size) { Game_BaseItem.new }
    equips.each_with_index do |item_id, i|
      etype_id = index_to_etype_id(i)
      slot_id = empty_slot(etype_id)
      @equips[slot_id].set_equip(etype_id == 0, item_id) if slot_id
    end
    refresh
  end


Initialising equipment takes one parameter, equips, which we called as part of the actor's setup method. The argument, if you recall, was actor.equips, which is an array of item IDs for each slot in the equipment part of the character tab.

First, we set the instance variable @equips to an array of Game_BaseItem.new; the number of elements will be the return value from equip_slots.size, which as we'll see in a second will always return 5 by default but you may be using equipment scripts that change this.

We then iterate through each element of equips with an index, assigning the name item_id to the current iterated equipment ID, and i to the index. The equipment type ID is set to the result of calling index_to_etpe_id (which figures out whether we need 0 for a weapon or 1 for a shield on the basis of whether the character is a dual wielder), and slot_id set to the result of calling empty_slot with the equipment type ID (which will ideally give us the first empty equipment slot matching the equipment type). Then, we call set_equip on @equips with the arguments etype_id == 0 (which will either be true or false) and item_id, but only if slot_id was successfully assigned.

Finally, we call refresh, which aside from calling its superclass refresh method will also make sure any items that can't be equipped by the character are immediately removed.

def index_to_etype_id(index)
    index == 1 && dual_wield? ? 0 : index
  end


This method converts the equip index from the editor into an equipment type ID. It's only used at the moment for changing the "shield" slot of a dual wielder to a "weapon" one. You've seen ternary operators many times by now, so you should be able to tell that it's checking whether the index argument is equal to one AND the actor dual wields; if so, it returns 0 (weapon), otherwise it returns index. Honestly, considering that there is literally only one situation in which this needs to be a thing, I would probably have gotten rid of this method and just changed the line in init_equips to etype_id = i == 1 && dual_wield? ? 0 : i, but that's just me.

def slot_list(etype_id)
    result = []
    equip_slots.each_with_index {|e, i| result.push(i) if e == etype_id }
    result
  end


This method converts the equipment type into a list of equipment slots which match it. First we set a variable called result to an empty array, then iterate through equip_slots with an index (we'll see equip_slots shortly).

The block used in the each_with_index uses e for the current element and i for the index. i is pushed to result if e is equal to the passed in etype_id. Shall we example? We shall.

Let's say we call slot_list(3). We loop through equip_slots and push the index of the current slot to result if its value is 3. This tells us which slots we can equip armour in!

def empty_slot(etype_id)
    list = slot_list(etype_id)
    list.find {|i| @equips[i].is_nil? } || list[0]
  end


This method converts the equipment type into a slot ID, with empty ones taking precedence. First we set list to slot_list(etype_id). As we've just seen, this gives us a list of slots which can equip the given type. Going back to our example, we would have a single-element array, [3]. Then we call the find method on list, supplying i as the element variable and returning the first slot ID where @equips[i] is found to be true. If no slot is nil, we return the first element of list.

def equip_slots
    return [0,0,2,3,4] if dual_wield?       # Dual wield
    return [0,1,2,3,4]                      # Normal
  end


Here we have equip_slots, which returns an array consisting of the equipment type IDs which can be equipped in each character equipment slot. By default, the slots are: weapon, shield, helmet, armour, accessory. Note that the only difference between the return values is that dual wielders have a 0 instead of a 1 for the shield slot, meaning they can equip two weapons.

def weapons
    @equips.select {|item| item.is_weapon? }.collect {|item| item.object }
  end


This method gets an array of weapon objects; first we call select on @equips with the block variable item; item will be added to the returned array if is_weapon? returns true. Then we call collect on the returned array, again using item as a block variable. This time, the new array will be populated by each item's object.

def armors
    @equips.select {|item| item.is_armor? }.collect {|item| item.object }
  end


This method is identical, but for armors.

def equips
    @equips.collect {|item| item.object }
  end


This method returns an array of item objects for the actor's equipment. It's essentially the same as weapons and armors without the equip type check.

def equip_change_ok?(slot_id)
    return false if equip_type_fixed?(equip_slots[slot_id])
    return false if equip_type_sealed?(equip_slots[slot_id])
    return true
  end


This method checks whether it is possible to change the equipment in a given slot ID. We return false if the slot in question has been fixed or sealed, and true otherwise.

def change_equip(slot_id, item)
    return unless trade_item_with_party(item, equips[slot_id])
    return if item && equip_slots[slot_id] != item.etype_id
    @equips[slot_id].object = item
    refresh
  end


This method changes the currently-equipped item in a given slot ID with a given item. Returns nil unless we are able to trade the item we want to equip and the item currently equipped with the party (in other words, we try to take the new item from the party's stash, and put the old one back). We also return nil if an item has been supplied and it isn't the same type as the type that can be equipped in that slot. If we get past those checks, we set the object equipped in the given slot ID to the supplied item.

def trade_item_with_party(new_item, old_item)
    return false if new_item && !$game_party.has_item?(new_item)
    $game_party.gain_item(old_item, 1)
    $game_party.lose_item(new_item, 1)
    return true
  end


This method trades an item with the party, which is to say it takes one from the communal party inventory and gives back a previously equipped one. First, we return false if a new item has been provided and the party doesn't possess it (we can't equip what we don't have). If that check is passed, the party gains one of the old item and loses one of the new item. (note that we can use nil for the new item to simply get rid of the old item, which is what happens if you unequip something without equipping something in its place).

def change_equip_by_id(slot_id, item_id)
    if equip_slots[slot_id] == 0
      change_equip(slot_id, $data_weapons[item_id])
    else
      change_equip(slot_id, $data_armors[item_id])
    end
  end


This method changes equipment via ID rather than passing in an item object. If the given equip slot is 0, we call change equip and pass in $data_weapons[item_id]. Otherwise, we pass in $data_armors[item_id]. The rationale is that if it's not a weapon it has to be an armor. This method is used for the "change equipment" event command in Game_Interpreter, as it only holds the item's ID and not the object for it.

def discard_equip(item)
    slot_id = equips.index(item)
    @equips[slot_id].object = nil if slot_id
  end


This method, as the name suggests, discards a piece of equipment, taking an item object as its argument. First, slot_id is set to the index of equips which holds the item supplied. Then, @equips[slot_id]'s object is set to nil (but only if the item was actually found in a slot)

def release_unequippable_items(item_gain = true)
    loop do
      last_equips = equips.dup
      @equips.each_with_index do |item, i|
        if !equippable?(item.object) || item.object.etype_id != equip_slots[i]
          trade_item_with_party(nil, item.object) if item_gain
          item.object = nil
        end
      end
      return if equips == last_equips
    end
  end


This method unequips anything an actor is unable to equip for whatever reason. First, it starts an infinite loop. Don't worry, the end condition comes later. Then, we set last_equips to a duplicate of equips. dup creates what's called a "shallow copy" of a variable, so we can make changes to it without changing the referenced object. We iterate through each element of @equips with an index, with item as the current element and i as the index:

If the item's object is unequippable or its equipment type is not the same as the type which can be equipped in the current slot, we trade the item with the party (only if item_gain is true) and then set item's object to nil. Then, we return if equips is equal to last_equips. This means the loop will continue running for as long as the actor has illegal equipment to remove.

def clear_equipments
    equip_slots.size.times do |i|
      change_equip(i, nil) if equip_change_ok?(i)
    end
  end


This method removes all equipment from an actor. Simply loops a number of times equal to the size of equip_slots with i as the block variable, and calls change_equip to change the slot to nil if it's okay to change equipment in that slot.

def optimize_equipments
    clear_equipments
    equip_slots.size.times do |i|
      next if !equip_change_ok?(i)
      items = $game_party.equip_items.select do |item|
        item.etype_id == equip_slots[i] &&
        equippable?(item) && item.performance >= 0
      end
      change_equip(i, items.max_by {|item| item.performance })
    end
  end


This method picks what the engine considers is the best weapon for the actor. First, we clear all equipment using the method we just looked at. Then, we loop a number of times equal to the number of equip slots with i as the block variable (look familiar):

We go to the next iteration if it's not okay to change equipment in the slot we're currently looking at. If we pass that test, we call select on the party's entire list of weapons and armors with item as a block variable; the item will be added to the returned array if the current item's equipment type ID is the same as that of the current slot, the item is equippable and the item's performance rating is greater than or equal to 0. The returned array is stored in a variable called items.

Finally, we call change_equip with i as the slot and the item being equipped is determined by calling max_by on items with item as the block variable. max_by returns the object from the supplied Enumerable (in this case the items array) which returns the maximum value. In other words, we'll be equipping whichever item has the highest performance rating.

Here's the thing...this is the default code for calculating performance:

weapons:
def performance
    params[2] + params[4] + params.inject(0) {|r, v| r += v }
  end


armors:
def performance
    params[3] + params[5] + params.inject(0) {|r, v| r += v }
  end


In other words, if it's a weapon we take its attack power + its magic attack power + the combined value of all its parameters (atk, def, matk, mdef, agi, luk, max hp, max mp). If it's armor, we take its defence + its magic defence + the combined value of its parameters.

Unfortunately, this means that the default engine pays literally no attention to traits on equipment. You could, for example, have two swords available to Eric: one with +20 atk and one with +19 ATK which quadruples his HP (via a trait), makes him immune to poison, gives his attacks the fire element, and doubles the gold the party gets in battles.

Guess which one the "optimise" button will give him?

I wasn't too happy about this, so when I noticed it I wrote a script that will take traits into account when calculating performance ratings, which is available here.

def skill_wtype_ok?(skill)
    wtype_id1 = skill.required_wtype_id1
    wtype_id2 = skill.required_wtype_id2
    return true if wtype_id1 == 0 && wtype_id2 == 0
    return true if wtype_id1 > 0 && wtype_equipped?(wtype_id1)
    return true if wtype_id2 > 0 && wtype_equipped?(wtype_id2)
    return false
  end


This method determines whether the weapon required for a given skill is equipped on the actor. It sets wtype_id1 and wtype_id2 to the weapon type IDs set as the ones required to use the skill in the database. We return true if both IDs are 0 (in other words, no weapon type is required); we return true if there's an id1 set and the actor has that type of weapon equipped; and we return true if there's an id2 set and the actor has that type of weapon equipped. Otherwise, we return false.

def wtype_equipped?(wtype_id)
    weapons.any? {|weapon| weapon.wtype_id == wtype_id }
  end


This method is the one which determines whether a given weapon type ID is equipped. It checks in weapons using the any method, passing in a block that uses weapon as a block variable holding the current weapon. It will return true if any iteration passes the check that the weapon's type ID matches the one that was passed in.

def refresh
    release_unequippable_items
    super
  end


Refresh method for actors. All it does is unequip items they can't have equipped and then calls the refresh method of Game_Battler.

def actor?
    return true
  end


This is a boolean method which returns true. It overloads the actor? method from Game_BattlerBase, which you'll recall returned false (because we didn't know at that point whether the instance was an actor or not, but now we do).

def friends_unit
    $game_party
  end


This method determines what is considered the "friend unit" for an actor; it returns $game_party. If you'll recall when we looked at Game_Action, it also had a method called friends_unit which returns subject.friends_unit. This is the method it's calling when the subject is an actor.

def opponents_unit
    $game_troop
  end


Identical method, but for the opponents unit instead. Returns $game_troop, obviously.

def id
    @actor_id
  end


Getter method for @actor_id.

def index
    $game_party.members.index(self)
  end


This method returns the index of the actor by looking them up in $game_party's members and returning the index of self.

def battle_member?
    $game_party.battle_members.include?(self)
  end


This boolean method determines whether the actor is a battle member, which will return true if the battle_members array in $game_party includes self.

def class
    $data_classes[@class_id]
  end


This method gets the actor's class object; returns the @class_id element from $data_classes.

def skills
    (@skills | added_skills).sort.collect {|id| $data_skills[id] }
  end


First, we take (@skills | added_skills) which performs what's called a bitwise OR on the two lists, basically combining them into one array of all the skill IDs known to the actor (we looked at added_skills back when we covered Game_BattlerBase). This array is sorted, then collect is called on it, using id as the block variable. Each element is replaced with $data_skills[id], giving us an array of skill objects.

def usable_skills
    skills.select {|skill| usable?(skill) }
  end


Gets an array of skills which can be used by the actor using select, the block returning true for the "skill" element if usable?(skill) returns true.

def feature_objects
    super + [actor] + [self.class] + equips.compact
  end


Here's another old one from Game_BattlerBase. Remember how feature_objects used to simply be a list of whatever states the battler had? Well now we're adding to the array the actor object, the actor's class, and a compacted list of equipment (removing any empty slots)

def atk_elements
    set = super
    set |= [1] if weapons.compact.empty?  # Unarmed: Physical element
    return set
  end


Yet again we overload a method from BattlerBase. First, set is set to super, which you may recall is the set of attack elements derived from features. (note that we've now added the actor, class and equips to the list of feature sources, so any attack elements included on these will be part of set now). Set is set to an array containing if there are no weapons equipped (unarmed = physical, like it says in the comment) but only if there wasn't already a value assigned to set by super. Finally, we return set.

def param_max(param_id)
    return 9999 if param_id == 0  # MHP
    return super
  end


This is...guess what? If you guessed "an overloaded method from Game_BattlerBase, Trihan?" you win a cookie! If you'll recall, the base version returned 999,999 if the parameter was max HP, 9,999 if it was max MP, and 999 otherwise. The actor version just overloads the max HP part to be 9999 instead, or returns the value from super otherwise. Obviously if the first line fires the "return 999999 if param_id == 0" from Game_BattlerBase will never be reached, because that case already resulted in a return value.

def param_base(param_id)
    self.class.params[param_id, @level]
  end


Overlooooooad. You'll recall that the original param_base simply returned 0. This time we're returning the parameter for the actor's class and level given the parameter's ID.

def param_plus(param_id)
    equips.compact.inject(super) {|r, item| r += item.params[param_id] }
  end


I don't think I need to say it any more. Okay, this overload, rather than just returning @param_plus, has a handy-dandy inject method! The initial value is super, so we've still got @param_plus, don't worry. We're then iterating through equips.compact (so we lost any nil slots) and adding the current equipment item's parameter increase for the given parameter to the returned array.

def atk_animation_id1
    if dual_wield?
      return weapons[0].animation_id if weapons[0]
      return weapons[1] ? 0 : 1
    else
      return weapons[0] ? weapons[0].animation_id : 1
    end
  end


This method determines the actor's normal attack animaton ID. If they're a dual wielder, we return the animation for their first weapon if they have one equipped, otherwise we return 0 if they have a second weapon equipped and 1 otherwise. If they aren't a dual wielder, we return the animation of the first weapon if they have one equipped and 1 otherwise. This results in the normal physical attack animation in cases where there isn't a weapon equipped in the first weapon slot.

def atk_animation_id2
    if dual_wield?
      return weapons[1] ? weapons[1].animation_id : 0
    else
      return 0
    end
  end


This method does a similar thing to work out the animation for the second weapon of a dual wielder. If dual wielding, returns the animation of the second weapon if one is equipped, or 0 otherwise. If they aren't a dual wielder, returns 0. Makes sense: they'll only have a second weapon if they dual wield.

def change_exp(exp, show)
    @exp[@class_id] = [exp, 0].max
    last_level = @level
    last_skills = skills
    level_up while !max_level? && self.exp >= next_level_exp
    level_down while self.exp < current_level_exp
    display_level_up(skills - last_skills) if show && @level > last_level
    refresh
  end


This method changes the actor's experience, taking exp and show as parameters; show determines whether there's a level up message when the actor's level changes. First, the @class_id index of @exp is set to the maximum value between the passed-in exp value and 0 (so we can't change it to a negative amount). last_level is set to the actor's level, and last_skills set to their current list of skills. We call level_up while we're not max level and exp is greater than or equal to the exp needed for the next level (or in other words, level up continuously until we don't have enough exp to do so any more), level_down while the exp is less than that needed for the current level, display_level_up if the flag is set and @level is greater than last_level. Note that the argument passed is skills - last_skills, as we only want to display the new skills that have been learned.

def exp
    @exp[@class_id]
  end


This is just a getter method for @exp, and returns the @class_id index from it.

def level_up
    @level += 1
    self.class.learnings.each do |learning|
      learn_skill(learning.skill_id) if learning.level == @level
    end
  end


This is the method for levelling up an actor. Adds 1 to @level, funnily enough. Then for each "learning" in the skills the actor's class can learn (set to the block variable learning) we call learn_skill with its skill ID if the actor's @level is now equal to the level the actor learns the skill at.

def level_down
    @level -= 1
  end


This method reduces an actor's level by 1.

def display_level_up(new_skills)
    $game_message.new_page
    $game_message.add(sprintf(Vocab::LevelUp, @name, Vocab::level, @level))
    new_skills.each do |skill|
      $game_message.add(sprintf(Vocab::ObtainSkill, skill.name))
    end
  end


This method displays a level up message and takes a list of newly-learned skills as its parameter. First we set a new page in $game_message, and add to it the levelup message set in Vocab, replacing the two occurrences of %s (if using the default message) with the actor's @name, whatever term was set in Vocab for levels, and the value of @level. You'll recall from Vocab that this message is in the form LevelUp = "%s is now %s %s!" so taking as an example Eric hitting level 6, the message will end up being "Eric is now level 6!" Simples!

After that, we iterate through each element of new_skills, assign it to "skill", and add a game message with another sprintf, this time for ObtainSkill from Vocab, and replacing the %s with the skill's name.

def gain_exp(exp)
    change_exp(self.exp + (exp * final_exp_rate).to_i, true)
  end


This method has the actor gain the given amount of exp, allowing for their experience rate (as we'll see in a second). It calls change_exp, which we've already looked at, supplying a value of the actor's current exp + (supplied exp * exp rate) as an integer, with the flag for displaying a levelup message set to true.

def final_exp_rate
    exr * (battle_member? ? 1 : reserve_members_exp_rate)
  end


This is the method which determines the actor's final experience rate. It returns EXR (EXperience Rate) * 1 if the actor is a battle member (present in one of the first four party slots), or the experience rate for reserve members otherwise.

def reserve_members_exp_rate
    $data_system.opt_extra_exp ? 1 : 0
  end


This method determines the experience rate for reserve members (those in the party but not present in the battle lineup). Returns 1 if the "Reserve Members' EXP" checkbox in the system tab of the database has been checked, or 0 otherwise.

def change_level(level, show)
    level = [[level, max_level].min, 1].max
    change_exp(exp_for_level(level), show)
  end


This method is similar to the exp change but allows you to change level directly. Takes two parameters, the level to change to and a flag to show levelups. Sets a variable called level (this is NOT the instance variable @level) to the minimum value between level and max_level (to prevent a value above the maximum) then the maximum value between that value and 1 (to prevent a value of 0 or less). You may see the structure [value, max_value].min, 1].max or similar in several places in Ruby scripts; it's a really handy way to make sure a parameter value is in a logical range and is basically a shorthand way of writing something like:

if level > max_level
level = max_level
elsif level < 1
level = 1
end

After this, we call change_exp using the return value of exp_for_level passing in the value of the level variable, and whatever value was passed in for show (either true or false).

def learn_skill(skill_id)
    unless skill_learn?($data_skills[skill_id])
      @skills.push(skill_id)
      @skills.sort!
    end
  end


This method teaches the actor a skill with the given ID. The block will only be executed if skill_learn? returns false when passed in the skill's object from the database. (we could also have achieved this with if skill_learn?($data_skills[skill_id]) == false) but unless gives us a shorter way to write the same condition, which half the time is Ruby's entire mission statement.

Anyway, the block pushes the skill's ID to @skills, and then sorts it in place. This ensures that the skills are always in database order regardless of the order they're learned in.

def forget_skill(skill_id)
    @skills.delete(skill_id)
  end


Deleting skills is way easier. It literally just deletes skill_id from the @skills array.

def skill_learn?(skill)
    skill.is_a?(RPG::Skill) && @skills.include?(skill.id)
  end


This method determines whether a skill has already been learned, and takes a skill object as a parameter. Checks whether the skill's class is RPG::Skill AND the actor's skill list includes the skill's ID.

def description
    actor.description
  end


Simple getter method for the actor's description.

def change_class(class_id, keep_exp = false)
    @exp[class_id] = exp if keep_exp
    @class_id = class_id
    change_exp(@exp[@class_id] || 0, false)
    refresh
  end


This method changes the actor's class to a given ID, with a flag to determine whether they keep their gained exp in the new class or not.

Sets @exp of the current class ID to the current exp value if keep_exp was true, changes @class_id to the new ID, then calls change_exp passing either the @class_id element of @exp OR 0 (this is to catch cases where the character has no experience in the new class) and false for the levelup message. Finally we call the actor's refresh method.

def set_graphic(character_name, character_index, face_name, face_index)
    @character_name = character_name
    @character_index = character_index
    @face_name = face_name
    @face_index = face_index
  end


This method sets the graphics for the actor. Takes as its parameters a charset name, the index to use, a faceset graphic, and an index to use from that. Then it simply sets the appropriate instance variables to the values that were passed in.

def use_sprite?
    return false
  end


This method determines whether the actor uses a sprite, which is used in Sprite_Battler. As you can see, this method returns false, because actors in VXA don't use battle sprites. When we look at Game_Enemy you'll see that the same method returns true, because funnily enough enemies DO use battle sprites. As may be apparent if you follow the logic of this, scripts which add battlers for actors (mainly side-view ones, obviously) tend to overload this method to return a true value instead.

def perform_damage_effect
    $game_troop.screen.start_shake(5, 5, 10)
    @sprite_effect_type = :blink
    Sound.play_actor_damage
  end


This method performs the screen effect seen in battle when an actor takes damage (a screen shake and short blink, followed by playing the actor damage sound effect). We'll look at these sprite effect types when we get to the Sprite classes.

def perform_collapse_effect
    if $game_party.in_battle
      @sprite_effect_type = :collapse
      Sound.play_actor_collapse
    end
  end


This method performs the "collapse" effect, which happens when a battler dies. If the party is in battle, the sprite effect type is set to :collapse (which handles the screen effect later) and we play the actor collapse sound effect.

def make_action_list
    list = []
    list.push(Game_Action.new(self).set_attack.evaluate)
    usable_skills.each do |skill|
      list.push(Game_Action.new(self).set_skill(skill.id).evaluate)
    end
    list
  end


This method creates a list of possible actions for use in autobattle. Sets list to an empty array, then creates a new instance of Game_Action pointing at self and calls its set_attack method (which sets the skill to normal attack) and calls the evaluate method on that, which we already looked at in Game_Action. (this basically adds normal attack to the possible actions list, evaluates it and pushes the result to list. Remember from Game_Action that each of these two methods return "self" at the end, so the return value is always the action's object with the updated variables). After that, we iterate through each usable skill with "skill" and again push a new Game_Action with the skill set to the current skill's ID and evaluated. Finally, we return list.

The end result of this is that we have a list of skills the actor is able to use, along with simulated potential results for using those skills on the most effective target.

def make_auto_battle_actions
    @actions.size.times do |i|
      @actions[i] = make_action_list.max_by {|action| action.value }
    end
  end


This method creates the actual actions for an autobattler. For a number of times equal to the size of @actions (which at the moment contains as many elements as the actor has attacks) set the i element of @actions to the maximum result of calling make_action_list and comparing their values. (in other words, for each action the actor can take, store the best possible action from a simulation)

def make_confusion_actions
    @actions.size.times do |i|
      @actions[i].set_confusion
    end
  end


This one's similar but calls set_confusion instead. We'll see how the engine makes the distinction next.

def make_actions
    super
    if auto_battle?
      make_auto_battle_actions
    elsif confusion?
      make_confusion_actions
    end
  end


To make the actions list, first we call super, which sets up @actions if we remember back to the make_actions method in Game_Battler. Then, if the actor autobattles, we call make_auto_battle_actions, otherwise if they're confused we call make_confusion_actions. Confused yet?

def on_player_walk
    @result.clear
    check_floor_effect
    if $game_player.normal_walk?
      turn_end_on_map
      states.each {|state| update_state_steps(state) }
      show_added_states
      show_removed_states
    end
  end


This method processes whenever the player takes a step. First, we clear the actor's Game_Result instance. Then we call check_floor_effect, which as we'll see shortly basically just damages them if the player is on a damaging floor. Then, if the player isn't in a vehicle or being forced to move via an event, we call turn_end_on_map, which youll see the effect of, again, in a second. After that, we iterate through each of the actor's states and call update_state_steps with the state as the argument. Finally, we call show_added_states and show_removed_states.

def update_state_steps(state)
    if state.remove_by_walking
      @state_steps[state.id] -= 1 if @state_steps[state.id] > 0
      remove_state(state.id) if @state_steps[state.id] == 0
    end
  end


This method is called every step and updates a given state. If the state is set to be removed by walking, we reduce @state_steps for the state's ID by 1 if the steps are greater than 0. We then remove the state if its steps are equal to 0.

def show_added_states
    @result.added_state_objects.each do |state|
      $game_message.add(name + state.message1) unless state.message1.empty?
    end
  end


This method shows states that have been added by walking. It's odd that this is here, because I don't think the default system CAN add a state when walking. But anyway!

We iterate through the added state objects of the actor's Game_Result with "state"; in each iteration we add a game message with the actor's name and the state's message 1 (unless it doesn't have one). This concatenation is the reason you need to enter a space at the beginning of the state messages for it to appear properly.

def show_removed_states
    @result.removed_state_objects.each do |state|
      $game_message.add(name + state.message4) unless state.message4.empty?
    end
  end


This method is similar but for showing removed states, which obviously CAN happen when walking. Iterates through each removed state and shows the actor's name with message4 (unless there isn't one).

def steps_for_turn
    return 20
  end


This method returns the number of steps which are regarded as one "turn" in battle, which by default is 20.

def turn_end_on_map
    if $game_party.steps % steps_for_turn == 0
      on_turn_end
      perform_map_damage_effect if @result.hp_damage > 0
    end
  end


This method performs "end of turn processing" on the map, as it were. If the party's steps divided by the steps for a turn gives a remainder of 0 (gotta love modulus!) we call on_turn_end (which we looked at when we covered Game_Battler) and performs the map damage effect if the result's damage is greater than 0.

def check_floor_effect
    execute_floor_damage if $game_player.on_damage_floor?
  end


This is the method we called in on_player_walk. Calls execute_floor_damage if the player is on a damaging floor.

def execute_floor_damage
    damage = (basic_floor_damage * fdr).to_i
    self.hp -= [damage, max_floor_damage].min
    perform_map_damage_effect if damage > 0
  end


This method actually executes the floor damage. Damage is set to the basic floor damage (coming up next method) multiplied by the actor's FDR (Floor Damage Rate) as an integer. Then the actor's HP is reduced by the minimum value between damage and the maximum floor damage (coming two methods from now) and if the damage is greater than 0 we call perform_map_damage_effect (same as in the turn end method, and that one's coming three methods from now!)

def basic_floor_damage
    return 10
  end


Method to determine base floor damage. It's 10.

def max_floor_damage
    $data_system.opt_floor_death ? hp : [hp - 1, 0].max
  end


This method gets the maximum floor damage. If the developer has checked "K.O. by Floor Damage" in the system tab, it's the value of hp; otherwise, it's the maximum value between hp-1 and 0. This means that with that checkbox unchecked floor damage can never be higher than 1 less than the actor's HP, and if they're at 1HP already they won't lose more.

def perform_map_damage_effect
    $game_map.screen.start_flash_for_damage
  end


This is the method for performing the map damage effect. Simply calls start_flash_for_damage in $game_map.screen. Although we haven't looked at $game_map yet (6 classes to go before we get there) we HAVE looked at Game_Screen, which Game_Map's screen property is an instance of. So we actually did cover this method way back when. If you've forgotten, it flashes the screen pure red at 128 alpha (half transparent) for 8 frames, or just over a 7th of a second.

def clear_actions
    super
    @action_input_index = 0
  end


This method clears the actor's actions. Calls the clear_actions method of the superclass (which clears the @actions array) and then sets the actor's action input index to 0.

def input
    @actions[@action_input_index]
  end


This method gets the actor's current input. Returns the @action_input_index element of @actions.

def next_command
    return false if @action_input_index >= @actions.size - 1
    @action_input_index += 1
    return true
  end


This method gets the actor's next command. Returns false if the action input index is greater than or equal to 1 less than the size of the @actions array (in other words, we don't have a next command if the actor has already input all the commands they're entitled to). If we pass that check, we increase the action input index by 1 and return true.

def prior_command
    return false if @action_input_index <= 0
    @action_input_index -= 1
    return true
  end


This is the opposite of the next comand method, funnily enough. Returns false if the action input index is less than or equal to 0. Passing that check reduces the action input index by 1 and returns true.

Phew! Finally, we're done with Game_Actor! I think this has been enough content that I should probably leave it as its own edition. As always, feel free to give comments and suggestions on what you think of the series so far and what you'd like to see from it in the future, especially once we're done with the default classes. Until next time.